feat(webapp): customer/vendor form ux improvement (#1053)
* feat(webapp): customer/vendor form ux improvement
This commit is contained in:
+3
-1
@@ -23,7 +23,9 @@
|
||||
"tenants:migrate:latest": "lerna run cli:tenants:migrate:latest --scope \"@bigcapital/server\"",
|
||||
"system:seed:latest": "lerna run cli:system:seed:latest --scope \"@bigcapital/server\"",
|
||||
"tenants:seed:latest": "lerna run cli:tenants:seed:latest --scope \"@bigcapital/server\"",
|
||||
"generate:sdk-types": "lerna run openapi:export --scope \"@bigcapital/server\" && lerna run generate --scope \"@bigcapital/sdk-ts\" && lerna run build --scope \"@bigcapital/sdk-ts\""
|
||||
"generate:sdk-types": "lerna run openapi:export --scope \"@bigcapital/server\" && lerna run generate --scope \"@bigcapital/sdk-ts\" && lerna run build --scope \"@bigcapital/sdk-ts\"",
|
||||
"format": "lerna run format",
|
||||
"format:check": "lerna run format:check"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.4.2",
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.alterTable('contacts', table => {
|
||||
table.string('code').nullable().unique();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.alterTable('contacts', table => {
|
||||
table.dropColumn('code');
|
||||
});
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IsEmail, IsOptional, IsString } from 'class-validator';
|
||||
import { IsEmail, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional } from '@/common/decorators/Validators';
|
||||
|
||||
export class ContactAddressDto {
|
||||
@ApiProperty({ required: false, description: 'Billing address line 1' })
|
||||
|
||||
@@ -155,4 +155,13 @@ export class CreateCustomerDto extends ContactAddressDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Customer code',
|
||||
example: 'CUST-001',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
code?: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { IsBoolean, IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ContactAddressDto } from './ContactAddress.dto';
|
||||
import { IsOptional } from '@/common/decorators/Validators';
|
||||
|
||||
export class EditCustomerDto extends ContactAddressDto {
|
||||
@ApiProperty({ required: true, description: 'Customer type' })
|
||||
@@ -62,4 +63,9 @@ export class EditCustomerDto extends ContactAddressDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
|
||||
@ApiProperty({ required: false, description: 'Customer code' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
code?: string;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ export class Customer extends TenantBaseModel {
|
||||
note: string;
|
||||
active: boolean;
|
||||
|
||||
code?: string;
|
||||
|
||||
/**
|
||||
* Query builder.
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface ICustomerNewDTO extends IContactAddressDTO {
|
||||
|
||||
note?: string;
|
||||
active?: boolean;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface ICustomerEditDTO extends IContactAddressDTO {
|
||||
@@ -50,6 +51,7 @@ export interface ICustomerEditDTO extends IContactAddressDTO {
|
||||
|
||||
note?: string;
|
||||
active?: boolean;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface ICustomersFilter extends IDynamicListFilter {
|
||||
|
||||
@@ -115,4 +115,13 @@ export class CreateVendorDto extends ContactAddressDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Vendor code',
|
||||
example: 'VEND-001',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
code?: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto';
|
||||
import { IsEmail, IsString, IsBoolean, IsOptional } from 'class-validator';
|
||||
import { IsEmail, IsString, IsBoolean } from 'class-validator';
|
||||
import { IsOptional } from '@/common/decorators/Validators';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class EditVendorDto extends ContactAddressDto {
|
||||
@@ -60,4 +61,9 @@ export class EditVendorDto extends ContactAddressDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
|
||||
@ApiProperty({ required: false, description: 'Vendor code' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
code?: string;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ export class Vendor extends TenantBaseModel {
|
||||
note: string;
|
||||
active: boolean;
|
||||
|
||||
code?: string;
|
||||
|
||||
/**
|
||||
* Query builder.
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface IVendorNewDTO extends IContactAddressDTO {
|
||||
|
||||
note?: string;
|
||||
active?: boolean;
|
||||
code?: string;
|
||||
}
|
||||
export interface IVendorEditDTO extends IContactAddressDTO {
|
||||
salutation?: string;
|
||||
@@ -46,6 +47,7 @@ export interface IVendorEditDTO extends IContactAddressDTO {
|
||||
|
||||
note?: string;
|
||||
active?: boolean;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface IVendorsFilter extends IDynamicListFilter {
|
||||
|
||||
@@ -4,6 +4,11 @@ import { FSelect } from '../Forms';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
export type DisplayNameListItem = { label: string };
|
||||
type DisplayNameFormat = {
|
||||
format: string;
|
||||
values: Array<string | undefined>;
|
||||
required: number[];
|
||||
};
|
||||
|
||||
export interface DisplayNameListProps
|
||||
extends Omit<
|
||||
@@ -11,6 +16,47 @@ export interface DisplayNameListProps
|
||||
'items' | 'valueAccessor' | 'textAccessor' | 'labelAccessor'
|
||||
> {}
|
||||
|
||||
function useDisplayNameFormatOptions(
|
||||
salutation?: string,
|
||||
firstName?: string,
|
||||
lastName?: string,
|
||||
companyName?: string,
|
||||
): DisplayNameListItem[] {
|
||||
return useMemo(() => {
|
||||
const formats: DisplayNameFormat[] = [
|
||||
{
|
||||
format: '{1} {2} {3}',
|
||||
values: [salutation, firstName, lastName],
|
||||
required: [1],
|
||||
},
|
||||
{ format: '{1} {2}', values: [firstName, lastName], required: [] },
|
||||
{ format: '{1}, {2}', values: [firstName, lastName], required: [1, 2] },
|
||||
{ format: '{1}', values: [companyName], required: [1] },
|
||||
];
|
||||
|
||||
return formats
|
||||
.filter(
|
||||
(format) =>
|
||||
!format.values.some((value, index) => {
|
||||
return !value && format.required.indexOf(index + 1) !== -1;
|
||||
}),
|
||||
)
|
||||
.map((formatOption) => {
|
||||
const { format, values } = formatOption;
|
||||
let label = format;
|
||||
|
||||
values.forEach((value, index) => {
|
||||
const replaceWith = value || '';
|
||||
label = label.replace(`{${index + 1}}`, replaceWith).trim();
|
||||
});
|
||||
return {
|
||||
label: label.replace(/\s+/g, ' ').replace(/\s+,/g, ',').trim(),
|
||||
};
|
||||
})
|
||||
.filter(({ label }) => Boolean(label));
|
||||
}, [salutation, firstName, lastName, companyName]);
|
||||
}
|
||||
|
||||
export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
|
||||
const {
|
||||
values: {
|
||||
@@ -21,40 +67,11 @@ export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
|
||||
},
|
||||
} = useFormikContext<any>();
|
||||
|
||||
const formats = useMemo(
|
||||
() => [
|
||||
{
|
||||
format: '{1} {2} {3}',
|
||||
values: [salutation, firstName, lastName],
|
||||
required: [1],
|
||||
},
|
||||
{ format: '{1} {2}', values: [firstName, lastName], required: [] },
|
||||
{ format: '{1}, {2}', values: [firstName, lastName], required: [1, 2] },
|
||||
{ format: '{1}', values: [companyName], required: [1] },
|
||||
],
|
||||
[firstName, lastName, companyName, salutation],
|
||||
);
|
||||
|
||||
const formatOptions: DisplayNameListItem[] = useMemo(
|
||||
() =>
|
||||
formats
|
||||
.filter(
|
||||
(format) =>
|
||||
!format.values.some((value, index) => {
|
||||
return !value && format.required.indexOf(index + 1) !== -1;
|
||||
}),
|
||||
)
|
||||
.map((formatOption) => {
|
||||
const { format, values } = formatOption;
|
||||
let label = format;
|
||||
|
||||
values.forEach((value, index) => {
|
||||
const replaceWith = value || '';
|
||||
label = label.replace(`{${index + 1}}`, replaceWith).trim();
|
||||
});
|
||||
return { label: label.replace(/\s+/g, ' ') };
|
||||
}),
|
||||
[formats],
|
||||
const formatOptions = useDisplayNameFormatOptions(
|
||||
salutation,
|
||||
firstName,
|
||||
lastName,
|
||||
companyName,
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -62,6 +79,7 @@ export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
|
||||
items={formatOptions}
|
||||
valueAccessor={'label'}
|
||||
textAccessor={'label'}
|
||||
labelAccessor={'_label'}
|
||||
placeholder={intl.get('select_display_name_as')}
|
||||
filterable={false}
|
||||
{...restProps}
|
||||
|
||||
@@ -28,6 +28,7 @@ export function SalutationList({ ...restProps }: SalutationListProps) {
|
||||
items={items}
|
||||
valueAccessor={'key'}
|
||||
textAccessor={'label'}
|
||||
labelAccessor={'_label'}
|
||||
placeholder={intl.get('salutation')}
|
||||
filterable={false}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,153 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Row, Col } from '@/components';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { Row } from '@/components';
|
||||
|
||||
const CustomerBillingAddress = ({}) => {
|
||||
import CustomerBillingAddress from './CustomerBillingAddress';
|
||||
import CustomerShippingAddress from './CustomerShippingAddress';
|
||||
|
||||
export default function CustomerAddressTabs() {
|
||||
return (
|
||||
<div className={'tab-panel--address'}>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<h4>
|
||||
<T id={'billing_address'} />
|
||||
</h4>
|
||||
{/*------------ Billing Address country -----------*/}
|
||||
<FFormGroup
|
||||
name={'billing_address_country'}
|
||||
inline={true}
|
||||
label={<T id={'country'} />}
|
||||
>
|
||||
<FInputGroup name={'billing_address_country'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Billing Address 1 -----------*/}
|
||||
<FFormGroup
|
||||
name={'billing_address1'}
|
||||
label={<T id={'address_line_1'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FTextArea name={'billing_address1'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Billing Address 2 -----------*/}
|
||||
<FFormGroup
|
||||
name={'billing_address2'}
|
||||
label={<T id={'address_line_2'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FTextArea name={'billing_address2'} />
|
||||
</FFormGroup>
|
||||
{/*------------ Billing Address city -----------*/}
|
||||
<FFormGroup
|
||||
name={'billing_address_city'}
|
||||
label={<T id={'city_town'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'billing_address_city'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Billing Address state -----------*/}
|
||||
<FFormGroup
|
||||
name={'billing_address_state'}
|
||||
label={<T id={'state'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'billing_address_state'} />
|
||||
</FFormGroup>
|
||||
{/*------------ Billing Address postcode -----------*/}
|
||||
<FFormGroup
|
||||
name={'billing_address_postcode'}
|
||||
label={<T id={'zip_code'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'billing_address_postcode'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Billing Address phone -----------*/}
|
||||
<FFormGroup
|
||||
name={'billing_address_phone'}
|
||||
label={<T id={'phone'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'billing_address_phone'} />
|
||||
</FFormGroup>
|
||||
</Col>
|
||||
|
||||
<Col xs={6}>
|
||||
<h4>
|
||||
<T id={'shipping_address'} />
|
||||
</h4>
|
||||
{/*------------ Shipping Address country -----------*/}
|
||||
<FFormGroup
|
||||
name={'shipping_address_country'}
|
||||
label={<T id={'country'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'shipping_address_country'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Shipping Address 1 -----------*/}
|
||||
<FFormGroup
|
||||
name={'shipping_address1'}
|
||||
label={<T id={'address_line_1'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FTextArea name={'shipping_address1'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Shipping Address 2 -----------*/}
|
||||
<FFormGroup
|
||||
name={'shipping_address2'}
|
||||
label={<T id={'address_line_2'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FTextArea name={'shipping_address2'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Shipping Address city -----------*/}
|
||||
<FFormGroup
|
||||
name={'shipping_address_city'}
|
||||
label={<T id={'city_town'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'shipping_address_city'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Shipping Address state -----------*/}
|
||||
<FFormGroup
|
||||
name={'shipping_address_state'}
|
||||
label={<T id={'state'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'shipping_address_state'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Shipping Address postcode -----------*/}
|
||||
<FFormGroup
|
||||
name={'shipping_address_postcode'}
|
||||
label={<T id={'zip_code'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'shipping_address_postcode'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Shipping Address phone -----------*/}
|
||||
<FFormGroup
|
||||
name={'shipping_address_phone'}
|
||||
label={<T id={'phone'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'shipping_address_phone'} />
|
||||
</FFormGroup>
|
||||
</Col>
|
||||
<CustomerBillingAddress />
|
||||
<CustomerShippingAddress />
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerBillingAddress;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Box } from '@/components';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||
|
||||
export function CustomerBillingAddress() {
|
||||
return (
|
||||
<Box data-section-id="billingAddress">
|
||||
<CustomerFormSectionTitle>
|
||||
<T id={'billing_address'} />
|
||||
</CustomerFormSectionTitle>
|
||||
<FFormGroup
|
||||
name={'billing_address_country'}
|
||||
label={<T id={'country'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'billing_address_country'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address1'}
|
||||
label={<T id={'address_line_1'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FTextArea name={'billing_address1'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address2'}
|
||||
label={<T id={'address_line_2'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FTextArea name={'billing_address2'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_city'}
|
||||
label={<T id={'city_town'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'billing_address_city'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_state'}
|
||||
label={<T id={'state'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'billing_address_state'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_postcode'}
|
||||
label={<T id={'zip_code'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'billing_address_postcode'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_phone'}
|
||||
label={<T id={'phone'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'billing_address_phone'} fill />
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
@@ -11,52 +10,36 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||
import { safeInvoke } from '@/utils';
|
||||
|
||||
/**
|
||||
* Customer floating actions bar.
|
||||
*/
|
||||
export default function CustomerFloatingActions({ onCancel }) {
|
||||
export function CustomerFloatingActions() {
|
||||
// Customer form context.
|
||||
const { isNewMode, setSubmitPayload } = useCustomerFormContext();
|
||||
const { isNewMode, setSubmitPayload } = useCustomerFormContext() as {
|
||||
isNewMode: boolean;
|
||||
setSubmitPayload: (payload: { noRedirect: boolean }) => void;
|
||||
};
|
||||
|
||||
// Formik context.
|
||||
const { resetForm, submitForm, isSubmitting } = useFormikContext();
|
||||
const { submitForm, isSubmitting } = useFormikContext();
|
||||
|
||||
// Handle submit button click.
|
||||
const handleSubmitBtnClick = (event) => {
|
||||
const handleSubmitBtnClick = (_event: React.MouseEvent<HTMLElement>) => {
|
||||
setSubmitPayload({ noRedirect: false });
|
||||
};
|
||||
|
||||
// Handle cancel button click.
|
||||
const handleCancelBtnClick = (event) => {
|
||||
safeInvoke(onCancel, event);
|
||||
};
|
||||
|
||||
// handle clear button clicl.
|
||||
const handleClearBtnClick = (event) => {
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// Handle submit & new button click.
|
||||
const handleSubmitAndNewClick = (event) => {
|
||||
const handleSubmitAndNewClick = (_event: React.MouseEvent<HTMLElement>) => {
|
||||
submitForm();
|
||||
setSubmitPayload({ noRedirect: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
<FloatingActionsGroup spacing={10}>
|
||||
<ButtonGroup>
|
||||
{/* ----------- Save and New ----------- */}
|
||||
<SaveButton
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
@@ -73,9 +56,9 @@ export default function CustomerFloatingActions({ onCancel }) {
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
minimal
|
||||
>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
@@ -84,24 +67,16 @@ export default function CustomerFloatingActions({ onCancel }) {
|
||||
/>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
|
||||
{/* ----------- Clear & Reset----------- */}
|
||||
<Button
|
||||
className={'ml1'}
|
||||
disabled={isSubmitting}
|
||||
onClick={handleClearBtnClick}
|
||||
text={!isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
|
||||
/>
|
||||
{/* ----------- Cancel ----------- */}
|
||||
<Button
|
||||
className={'ml1'}
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</Group>
|
||||
</FloatingActionsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const SaveButton = styled(Button)`
|
||||
min-width: 100px;
|
||||
`;
|
||||
const FloatingActionsGroup = styled(Group)`
|
||||
padding: 10px 0;
|
||||
padding-left: 165px;
|
||||
border-top: 1px solid #50555a;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: var(--color-card-background);
|
||||
z-index: 1;
|
||||
`;
|
||||
@@ -1,15 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { CustomerFormProvider } from './CustomerFormProvider';
|
||||
import CustomerFormFormik from './CustomerFormFormik';
|
||||
|
||||
/**
|
||||
* Abstracted customer form.
|
||||
*/
|
||||
export default function CustomerForm({ customerId }) {
|
||||
return (
|
||||
<CustomerFormProvider customerId={customerId}>
|
||||
<CustomerFormFormik />
|
||||
</CustomerFormProvider>
|
||||
);
|
||||
}
|
||||
+20
-8
@@ -6,34 +6,46 @@ import { FormattedMessage as T, FFormGroup, FInputGroup } from '@/components';
|
||||
|
||||
export default function CustomerFormAfterPrimarySection({}) {
|
||||
return (
|
||||
<div className={'customer-form__after-primary-section-content'}>
|
||||
<div>
|
||||
{/*------------ Customer email -----------*/}
|
||||
<FFormGroup
|
||||
name={'email'}
|
||||
label={<T id={'customer_email'} />}
|
||||
inline={true}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'email'} />
|
||||
<FInputGroup name={'email'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Phone number -----------*/}
|
||||
<FFormGroup
|
||||
name={'personal_phone'}
|
||||
label={<T id={'phone_number'} />}
|
||||
inline={true}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<ControlGroup>
|
||||
<ControlGroup fill>
|
||||
<FInputGroup
|
||||
name={'personal_phone'}
|
||||
placeholder={intl.get('personal')}
|
||||
fill
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'work_phone'}
|
||||
placeholder={intl.get('work')}
|
||||
fill
|
||||
/>
|
||||
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} />
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Customer website -----------*/}
|
||||
<FFormGroup name={'website'} label={<T id={'website'} />} inline={true}>
|
||||
<FInputGroup name={'website'} placeholder={'http://'} />
|
||||
<FFormGroup
|
||||
name={'website'}
|
||||
label={<T id={'website'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'website'} placeholder={'http://'} fill />
|
||||
</FFormGroup>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { ControlGroup, Divider, Icon as BlueprintIcon } from '@blueprintjs/core';
|
||||
import {
|
||||
Hint,
|
||||
FieldRequiredHint,
|
||||
SalutationList,
|
||||
DisplayNameList,
|
||||
FormattedMessage as T,
|
||||
FInputGroup,
|
||||
FFormGroup,
|
||||
Box,
|
||||
Icon,
|
||||
Stack,
|
||||
} from '@/components';
|
||||
import { CustomerTypeRadioField } from './CustomerTypeRadioField';
|
||||
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||
import { useAutofocus } from '@/hooks';
|
||||
|
||||
export function CustomerFormBasicSection({}) {
|
||||
const firstNameFieldRef = useAutofocus();
|
||||
|
||||
return (
|
||||
<Box data-section-id="primary">
|
||||
<CustomerFormSectionTitle>Customer details</CustomerFormSectionTitle>
|
||||
|
||||
{/**-----------Customer type. -----------*/}
|
||||
<CustomerTypeRadioField />
|
||||
|
||||
{/**----------- Contact name -----------*/}
|
||||
<FFormGroup
|
||||
name={'salutation'}
|
||||
label={<T id={'contact_name'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<ControlGroup fill>
|
||||
<SalutationList
|
||||
name={'salutation'}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'first_name'}
|
||||
placeholder={intl.get('first_name')}
|
||||
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
||||
fill
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'last_name'}
|
||||
placeholder={intl.get('last_name')}
|
||||
fill
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'code'}
|
||||
label={'Customer Code'}
|
||||
helperText="Add a unique account number to identify, reference and search for the contact."
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup
|
||||
name={'code'}
|
||||
fill />
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Company Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'company_name'}
|
||||
label={<T id={'company_name'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'company_name'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Display Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'display_name'}
|
||||
label={<T id={'display_name'} />}
|
||||
helperText="This is the name that appears on invoices and emails."
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<DisplayNameList
|
||||
name={'display_name'}
|
||||
popoverProps={{ minimal: true }}
|
||||
buttonProps={{ fill: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<Divider style={{ margin: '20px 0' }} />
|
||||
|
||||
{/*------------ Vendor email -----------*/}
|
||||
<FFormGroup
|
||||
name={'email'}
|
||||
label={<T id={'vendor_email'} />}
|
||||
inline
|
||||
>
|
||||
<FInputGroup
|
||||
name={'email'}
|
||||
leftIcon={<Icon icon="envelope" />}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Phone number -----------*/}
|
||||
<FFormGroup
|
||||
name={'work_phone'}
|
||||
className={'form-group--phone-number'}
|
||||
label={<T id={'phone_number'} />}
|
||||
inline={true}
|
||||
>
|
||||
<Stack spacing={10}>
|
||||
<FInputGroup
|
||||
name={'work_phone'}
|
||||
placeholder={intl.get('work')}
|
||||
leftIcon="phone"
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'personal_phone'}
|
||||
placeholder={intl.get('mobile')}
|
||||
/>
|
||||
</Stack>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Vendor website -----------*/}
|
||||
<FFormGroup name={'website'} label={<T id={'website'} />} inline={true}>
|
||||
<FInputGroup
|
||||
name={'website'}
|
||||
placeholder={'http://'}
|
||||
leftIcon={<BlueprintIcon icon="globe-network" />}
|
||||
/>
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Tab } from "@blueprintjs/core";
|
||||
import { Card, Group } from "@/components";
|
||||
import { Tabs } from "@blueprintjs/core";
|
||||
import { useState } from "react";
|
||||
import { css } from '@emotion/css';
|
||||
import { CustomerFloatingActions } from "./CustomerFloatingActions";
|
||||
import { CustomerFormSections } from "./CustomerFormFields";
|
||||
|
||||
export function CustomerFormContent() {
|
||||
const [selectedTabId, setSelectedTabId] = useState('primary');
|
||||
|
||||
const handleTabChange = (tabId: string) => {
|
||||
const sectionId = String(tabId);
|
||||
setSelectedTabId(sectionId);
|
||||
|
||||
const section = document.querySelector(
|
||||
`[data-section-id="${sectionId}"]`,
|
||||
);
|
||||
if (section) {
|
||||
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={css`padding-bottom: 0 !important;`}>
|
||||
<Group verticalAlign={'top'} alignItems={'flex-start'} flexWrap={'nowrap'}>
|
||||
<Tabs
|
||||
selectedTabId={selectedTabId}
|
||||
onChange={handleTabChange}
|
||||
className={css`position: sticky; top: 20px;`}
|
||||
vertical
|
||||
>
|
||||
<Tab id={'primary'} title={'Basic'} />
|
||||
<Tab id={'financial'} title={'Financial'} />
|
||||
<Tab id={'billingAddress'} title={'Billing address'} />
|
||||
<Tab id={'shippingAddress'} title={'Shipping address'} />
|
||||
<Tab id={'notes'} title={'Notes'} />
|
||||
</Tabs>
|
||||
|
||||
<CustomerFormSections />
|
||||
</Group>
|
||||
<CustomerFloatingActions />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Divider } from '@blueprintjs/core';
|
||||
import { css } from '@emotion/css';
|
||||
import { Box } from '@/components';
|
||||
|
||||
import { CustomerFormBasicSection } from './CustomerFormBasicSection';
|
||||
import { CustomerFormFinancialSection } from './CustomerFormFinancialSection';
|
||||
import { CustomerBillingAddress } from './CustomerBillingAddress';
|
||||
import { CustomerShippingAddress } from './CustomerShippingAddress';
|
||||
import { CustomerFormNotesSection } from './CustomerFormNotesSection';
|
||||
|
||||
const customerFormSectionDividerClass = css`
|
||||
margin: 20px 0;
|
||||
`;
|
||||
|
||||
export function CustomerFormSections() {
|
||||
return (
|
||||
<Box>
|
||||
<CustomerFormBasicSection />
|
||||
<Divider className={customerFormSectionDividerClass} />
|
||||
|
||||
<CustomerFormFinancialSection />
|
||||
<Divider className={customerFormSectionDividerClass} />
|
||||
|
||||
<CustomerBillingAddress />
|
||||
<Divider className={customerFormSectionDividerClass} />
|
||||
|
||||
<CustomerShippingAddress />
|
||||
<Divider className={customerFormSectionDividerClass} />
|
||||
|
||||
<CustomerFormNotesSection />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+33
-50
@@ -1,8 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FormGroup, Position, Classes, ControlGroup } from '@blueprintjs/core';
|
||||
import { FastField, ErrorMessage, useFormikContext } from 'formik';
|
||||
import { Position, ControlGroup } from '@blueprintjs/core';
|
||||
import { ErrorMessage, useFormikContext } from 'formik';
|
||||
import { Features } from '@/constants';
|
||||
import {
|
||||
FFormGroup,
|
||||
@@ -11,11 +10,11 @@ import {
|
||||
CurrencySelectList,
|
||||
BranchSelect,
|
||||
FeatureCan,
|
||||
Row,
|
||||
Col,
|
||||
FMoneyInputGroup,
|
||||
ExchangeRateInputGroup,
|
||||
FDateInput,
|
||||
Icon,
|
||||
Box,
|
||||
} from '@/components';
|
||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||
import {
|
||||
@@ -24,67 +23,56 @@ import {
|
||||
useSetPrimaryBranchToForm,
|
||||
} from './utils';
|
||||
import { useCurrentOrganization } from '@/hooks/state';
|
||||
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||
|
||||
/**
|
||||
* Customer financial panel.
|
||||
*/
|
||||
export default function CustomerFinancialPanel() {
|
||||
export function CustomerFormFinancialSection() {
|
||||
const { currencies, customerId, branches } = useCustomerFormContext();
|
||||
|
||||
// Sets the primary branch to form.
|
||||
useSetPrimaryBranchToForm();
|
||||
|
||||
return (
|
||||
<div className={'tab-panel--financial'}>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
{/*------------ Currency -----------*/}
|
||||
<Box data-section-id="financial">
|
||||
<CustomerFormSectionTitle>
|
||||
<T id={'financial'} />
|
||||
</CustomerFormSectionTitle>
|
||||
|
||||
<FFormGroup
|
||||
name={'currency_code'}
|
||||
label={<T id={'currency'} />}
|
||||
fastField
|
||||
inline
|
||||
>
|
||||
fill
|
||||
>
|
||||
<CurrencySelectList
|
||||
name="currency_code"
|
||||
items={currencies}
|
||||
disabled={customerId}
|
||||
/>
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Opening balance -----------*/}
|
||||
<CustomerOpeningBalanceField />
|
||||
|
||||
{/*------ Opening Balance Exchange Rate -----*/}
|
||||
<CustomerOpeningBalanceExchangeRateField />
|
||||
|
||||
{/*------------ Opening balance at -----------*/}
|
||||
<CustomerOpeningBalanceAtField />
|
||||
|
||||
{/*------------ Opening branch -----------*/}
|
||||
|
||||
<FeatureCan feature={Features.Branches}>
|
||||
<FFormGroup
|
||||
label={<T id={'customer.label.opening_branch'} />}
|
||||
name={'opening_balance_branch_id'}
|
||||
inline={true}
|
||||
inline
|
||||
>
|
||||
<BranchSelect
|
||||
name={'opening_balance_branch_id'}
|
||||
branches={branches}
|
||||
popoverProps={{ minimal: true }}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
</FeatureCan>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer opening balance at date field.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function CustomerOpeningBalanceAtField() {
|
||||
const { customerId } = useCustomerFormContext();
|
||||
|
||||
@@ -92,10 +80,11 @@ function CustomerOpeningBalanceAtField() {
|
||||
if (customerId) return null;
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
<FFormGroup
|
||||
name={'opening_balance_at'}
|
||||
label={<T id={'opening_balance_at'} />}
|
||||
inline={true}
|
||||
inline
|
||||
fill
|
||||
helperText={<ErrorMessage name="opening_balance_at" />}
|
||||
>
|
||||
<FDateInput
|
||||
@@ -104,16 +93,15 @@ function CustomerOpeningBalanceAtField() {
|
||||
disabled={customerId}
|
||||
formatDate={(date) => date.toLocaleDateString()}
|
||||
parseDate={(str) => new Date(str)}
|
||||
inputProps={{
|
||||
leftIcon: <Icon icon={'date-range'} />,
|
||||
}}
|
||||
fill={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer opening balance field.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function CustomerOpeningBalanceField() {
|
||||
const { customerId } = useCustomerFormContext();
|
||||
const { values } = useFormikContext();
|
||||
@@ -125,15 +113,17 @@ function CustomerOpeningBalanceField() {
|
||||
<FFormGroup
|
||||
label={<T id={'opening_balance'} />}
|
||||
name={'opening_balance'}
|
||||
inline={true}
|
||||
inline
|
||||
shouldUpdate={openingBalanceFieldShouldUpdate}
|
||||
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
||||
fastField={true}
|
||||
fill
|
||||
>
|
||||
<ControlGroup>
|
||||
<InputPrependText text={values.currency_code} />
|
||||
<InputPrependText text={values.currency_code as string} />
|
||||
<FMoneyInputGroup
|
||||
name={'opening_balance'}
|
||||
fastField
|
||||
inputGroupProps={{ fill: true }}
|
||||
/>
|
||||
</ControlGroup>
|
||||
@@ -141,11 +131,6 @@ function CustomerOpeningBalanceField() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer opening balance exchange rate field if the customer has foreign
|
||||
* currency.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function CustomerOpeningBalanceExchangeRateField() {
|
||||
const { values } = useFormikContext();
|
||||
const { customerId } = useCustomerFormContext();
|
||||
@@ -158,16 +143,14 @@ function CustomerOpeningBalanceExchangeRateField() {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FFormGroup
|
||||
label={' '}
|
||||
name={'opening_balance_exchange_rate'}
|
||||
inline={true}
|
||||
>
|
||||
|
||||
<ExchangeRateInputGroup
|
||||
fromCurrency={values.currency_code}
|
||||
toCurrency={currentOrganization.base_currency}
|
||||
name={'opening_balance_exchange_rate'}
|
||||
onRecalcConfirm={() => {}}
|
||||
onCancel={() => {}}
|
||||
formGroupProps={{ label: ' ' }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +1,95 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { Formik, Form } from 'formik';
|
||||
import { Formik, Form, FormikHelpers } from 'formik';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
|
||||
import { compose, transformToForm, saveInvoke, parseBoolean } from '@/utils';
|
||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||
import { defaultInitialValues } from './utils';
|
||||
|
||||
import { AppToaster } from '@/components';
|
||||
import CustomerFormPrimarySection from './CustomerFormPrimarySection';
|
||||
import CustomerFormAfterPrimarySection from './CustomerFormAfterPrimarySection';
|
||||
import CustomersTabs from './CustomersTabs';
|
||||
import CustomerFloatingActions from './CustomerFloatingActions';
|
||||
|
||||
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
||||
import { CustomerFormContent } from './CustomerFormContent';
|
||||
|
||||
import '@/style/pages/Customers/Form.scss';
|
||||
type CustomerFormValues = {
|
||||
customer_type: string;
|
||||
salutation: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
company_name: string;
|
||||
display_name: string;
|
||||
|
||||
/**
|
||||
* Customer form.
|
||||
*/
|
||||
function CustomerFormFormik({
|
||||
email?: string;
|
||||
work_phone?: string;
|
||||
personal_phone?: string;
|
||||
website?: string;
|
||||
note?: string;
|
||||
active: boolean | string;
|
||||
|
||||
billing_address_country: string;
|
||||
billing_address1: string;
|
||||
billing_address2: string;
|
||||
billing_address_city: string;
|
||||
billing_address_state: string;
|
||||
billing_address_postcode?: string;
|
||||
billing_address_phone?: string;
|
||||
|
||||
shipping_address_country: string;
|
||||
shipping_address1: string;
|
||||
shipping_address2: string;
|
||||
shipping_address_city: string;
|
||||
shipping_address_state: string;
|
||||
shipping_address_postcode?: string;
|
||||
shipping_address_phone?: string;
|
||||
|
||||
currency_code: string;
|
||||
opening_balance?: string | number;
|
||||
opening_balance_at?: string;
|
||||
opening_balance_exchange_rate?: string;
|
||||
opening_balance_branch_id?: string;
|
||||
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type CustomerFormSubmitPayload = {
|
||||
noRedirect?: boolean;
|
||||
};
|
||||
|
||||
type CustomerFormFormikRootProps = {
|
||||
organization: {
|
||||
base_currency: string;
|
||||
};
|
||||
|
||||
// #ownProps
|
||||
initialValues?: Partial<CustomerFormValues>;
|
||||
onSubmitSuccess?: (
|
||||
values: CustomerFormValues,
|
||||
formArgs: FormikHelpers<CustomerFormValues>,
|
||||
submitPayload: CustomerFormSubmitPayload,
|
||||
responseData?: unknown,
|
||||
) => void;
|
||||
onSubmitError?: (
|
||||
values: CustomerFormValues,
|
||||
formArgs: FormikHelpers<CustomerFormValues>,
|
||||
submitPayload: CustomerFormSubmitPayload,
|
||||
errorData?: unknown,
|
||||
) => void;
|
||||
onCancel?: () => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const EMPTY_INITIAL_VALUES: Partial<CustomerFormValues> = {};
|
||||
|
||||
function CustomerFormFormikRoot({
|
||||
organization: { base_currency },
|
||||
|
||||
// #ownProps
|
||||
initialValues: initialCustomerValues,
|
||||
initialValues: initialCustomerValues = EMPTY_INITIAL_VALUES,
|
||||
onSubmitSuccess,
|
||||
onSubmitError,
|
||||
onCancel,
|
||||
// `onCancel` is accepted for compatibility but currently not used.
|
||||
className,
|
||||
}) {
|
||||
}: CustomerFormFormikRootProps) {
|
||||
const {
|
||||
customer,
|
||||
submitPayload,
|
||||
@@ -44,28 +99,28 @@ function CustomerFormFormik({
|
||||
isNewMode,
|
||||
} = useCustomerFormContext();
|
||||
|
||||
/**
|
||||
* Initial values in create and edit mode.
|
||||
*/
|
||||
const initialValues = useMemo(
|
||||
const initialValues = useMemo<CustomerFormValues>(
|
||||
() => ({
|
||||
...defaultInitialValues,
|
||||
currency_code: base_currency,
|
||||
...transformToForm(contactDuplicate || customer, defaultInitialValues),
|
||||
...transformToForm(contactDuplicate ?? customer ?? {}, defaultInitialValues),
|
||||
...transformToForm(initialCustomerValues, defaultInitialValues),
|
||||
}),
|
||||
}) as CustomerFormValues,
|
||||
[customer, contactDuplicate, base_currency, initialCustomerValues],
|
||||
);
|
||||
|
||||
// Handles the form submit.
|
||||
const handleFormSubmit = (values, formArgs) => {
|
||||
const handleFormSubmit = (
|
||||
values: CustomerFormValues,
|
||||
formArgs: FormikHelpers<CustomerFormValues>,
|
||||
) => {
|
||||
const { setSubmitting, resetForm } = formArgs;
|
||||
const formValues = {
|
||||
...values,
|
||||
active: parseBoolean(values.active, true),
|
||||
};
|
||||
|
||||
const onSuccess = (res) => {
|
||||
const onSuccess = (res: { data?: unknown }) => {
|
||||
AppToaster.show({
|
||||
message: intl.get(
|
||||
isNewMode
|
||||
@@ -83,60 +138,40 @@ function CustomerFormFormik({
|
||||
setSubmitting(false);
|
||||
saveInvoke(onSubmitError, values, formArgs, submitPayload);
|
||||
};
|
||||
|
||||
if (isNewMode) {
|
||||
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
||||
} else {
|
||||
editCustomerMutate([customer.id, formValues])
|
||||
.then(onSuccess)
|
||||
.catch(onError);
|
||||
if (!customer) return;
|
||||
editCustomerMutate([customer.id, formValues]).then(onSuccess).catch(onError);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
CLASSES.PAGE_FORM,
|
||||
CLASSES.PAGE_FORM_CUSTOMER,
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Formik
|
||||
<Formik<CustomerFormValues>
|
||||
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
<Form>
|
||||
<CustomerFormHeaderPrimary>
|
||||
<CustomerFormPrimarySection />
|
||||
</CustomerFormHeaderPrimary>
|
||||
|
||||
<div className={'page-form__after-priamry-section'}>
|
||||
<CustomerFormAfterPrimarySection />
|
||||
</div>
|
||||
|
||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
||||
<CustomersTabs />
|
||||
</div>
|
||||
|
||||
<CustomerFloatingActions onCancel={onCancel} />
|
||||
<CustomerFormFields>
|
||||
<CustomerFormContent />
|
||||
</CustomerFormFields>
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const CustomerFormHeaderPrimary = styled.div`
|
||||
--x-border: #e4e4e4;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-border: var(--color-dark-gray3);
|
||||
const CustomerFormFields = styled.div`
|
||||
.bp4-form-content,
|
||||
.bp6-form-content {
|
||||
min-width: 300px;
|
||||
}
|
||||
.bp4-form-group{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.bp4-form-group.bp4-inline label.bp4-label {
|
||||
min-width: 140px;
|
||||
}
|
||||
padding: 10px 0 0;
|
||||
margin: 0 0 20px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid var(--x-border);
|
||||
max-width: 1000px;
|
||||
`;
|
||||
|
||||
export default compose(withCurrentOrganization())(CustomerFormFormik);
|
||||
export const CustomerFormFormik = compose(withCurrentOrganization(undefined))(CustomerFormFormikRoot);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Box, FFormGroup, FormattedMessage as T, FTextArea } from '@/components';
|
||||
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||
|
||||
export function CustomerFormNotesSection() {
|
||||
return (
|
||||
<Box data-section-id="notes">
|
||||
<CustomerFormSectionTitle>
|
||||
<T id={'notes'} />
|
||||
</CustomerFormSectionTitle>
|
||||
|
||||
<FFormGroup name={'note'} label={<T id={'note'} />} inline>
|
||||
<FTextArea name={'note'} fill />
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -2,73 +2,53 @@
|
||||
import React from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DashboardCard, DashboardInsider } from '@/components';
|
||||
|
||||
import CustomerFormFormik from './CustomerFormFormik';
|
||||
import { Box, DashboardCard, DashboardInsider } from '@/components';
|
||||
import { CustomerFormFormik, ustomerFormFormik } from './CustomerFormFormik';
|
||||
import {
|
||||
CustomerFormProvider,
|
||||
useCustomerFormContext,
|
||||
} from './CustomerFormProvider';
|
||||
|
||||
/**
|
||||
* Customer form page loading.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
function CustomerFormPageLoading({ children }) {
|
||||
const { isFormLoading } = useCustomerFormContext();
|
||||
|
||||
return (
|
||||
<CustomerDashboardInsider loading={isFormLoading}>
|
||||
{children}
|
||||
</CustomerDashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer form page.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
export default function CustomerFormPage() {
|
||||
const history = useHistory();
|
||||
const { id } = useParams();
|
||||
|
||||
const customerId = parseInt(id, 10);
|
||||
|
||||
// Handle the form submit success.
|
||||
const handleSubmitSuccess = (values, formArgs, submitPayload) => {
|
||||
if (!submitPayload.noRedirect) {
|
||||
history.push('/customers');
|
||||
}
|
||||
};
|
||||
// Handle the form cancel button click.
|
||||
const handleFormCancel = () => {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<CustomerFormProvider customerId={customerId}>
|
||||
<CustomerFormPageLoading>
|
||||
<DashboardCard page>
|
||||
<CustomerFormPageFormik
|
||||
onSubmitSuccess={handleSubmitSuccess}
|
||||
onCancel={handleFormCancel}
|
||||
/>
|
||||
</DashboardCard>
|
||||
</CustomerFormPageLoading>
|
||||
<CustomerFormPageContent />
|
||||
</CustomerFormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const CustomerFormPageFormik = styled(CustomerFormFormik)`
|
||||
.page-form {
|
||||
&__floating-actions {
|
||||
margin-left: -40px;
|
||||
margin-right: -40px;
|
||||
function CustomerFormPageContent() {
|
||||
const history = useHistory();
|
||||
const { isFormLoading } = useCustomerFormContext();
|
||||
|
||||
|
||||
const handleSubmitSuccess = (values, formArgs, submitPayload) => {
|
||||
if (!submitPayload.noRedirect) {
|
||||
history.push('/customers');
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CustomerDashboardInsider = styled(DashboardInsider)`
|
||||
padding-bottom: 64px;
|
||||
`;
|
||||
// Handle the form cancel button click.
|
||||
const handleFormCancel = () => {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<DashboardInsider loading={isFormLoading}>
|
||||
<Box mx={'auto'} maxWidth={800}>
|
||||
<CustomerFormFormik
|
||||
onSubmitSuccess={handleSubmitSuccess}
|
||||
onCancel={handleFormCancel}
|
||||
/>
|
||||
</Box>
|
||||
</DashboardInsider>
|
||||
)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core';
|
||||
import { FastField, Field, ErrorMessage } from 'formik';
|
||||
import {
|
||||
Hint,
|
||||
FieldRequiredHint,
|
||||
SalutationList,
|
||||
DisplayNameList,
|
||||
FormattedMessage as T,
|
||||
FInputGroup,
|
||||
FFormGroup,
|
||||
} from '@/components';
|
||||
import CustomerTypeRadioField from './CustomerTypeRadioField';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { inputIntent } from '@/utils';
|
||||
import { useAutofocus } from '@/hooks';
|
||||
|
||||
/**
|
||||
* Customer form primary section.
|
||||
*/
|
||||
export default function CustomerFormPrimarySection({}) {
|
||||
const firstNameFieldRef = useAutofocus();
|
||||
|
||||
return (
|
||||
<div className={'customer-form__primary-section-content'}>
|
||||
{/**-----------Customer type. -----------*/}
|
||||
<CustomerTypeRadioField />
|
||||
|
||||
{/**----------- Contact name -----------*/}
|
||||
<FFormGroup
|
||||
name={'salutation'}
|
||||
label={<T id={'contact_name'} />}
|
||||
inline={true}
|
||||
>
|
||||
<ControlGroup>
|
||||
<SalutationList
|
||||
name={'salutation'}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'first_name'}
|
||||
placeholder={intl.get('first_name')}
|
||||
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
||||
/>
|
||||
<FInputGroup name={'last_name'} placeholder={intl.get('last_name')} />
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Company Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'company_name'}
|
||||
label={<T id={'company_name'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'company_name'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Display Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'display_name'}
|
||||
label={
|
||||
<>
|
||||
<T id={'display_name'} />
|
||||
<FieldRequiredHint />
|
||||
<Hint />
|
||||
</>
|
||||
}
|
||||
inline={true}
|
||||
>
|
||||
<DisplayNameList
|
||||
name={'display_name'}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import React, { useState, createContext } from 'react';
|
||||
import React, { createContext, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
useCustomer,
|
||||
@@ -12,10 +11,60 @@ import {
|
||||
import { Features } from '@/constants';
|
||||
import { useFeatureCan } from '@/hooks/state';
|
||||
|
||||
const CustomerFormContext = createContext();
|
||||
type CustomerFormSubmitPayload = {
|
||||
noRedirect?: boolean;
|
||||
};
|
||||
|
||||
function CustomerFormProvider({ query, customerId, ...props }) {
|
||||
const { state } = useLocation();
|
||||
type Customer = {
|
||||
id: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type Currency = {
|
||||
currency_code: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type Branch = {
|
||||
id: number;
|
||||
primary?: boolean;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type CustomerFormContextValue = {
|
||||
customerId?: number;
|
||||
customer?: Customer;
|
||||
currencies: Currency[];
|
||||
branches: Branch[];
|
||||
contactDuplicate?: Customer;
|
||||
submitPayload: CustomerFormSubmitPayload;
|
||||
isNewMode: boolean;
|
||||
|
||||
isCustomerLoading: boolean;
|
||||
isCurrenciesLoading: boolean;
|
||||
isBranchesSuccess: boolean;
|
||||
isFormLoading: boolean;
|
||||
|
||||
setSubmitPayload: React.Dispatch<
|
||||
React.SetStateAction<CustomerFormSubmitPayload>
|
||||
>;
|
||||
|
||||
editCustomerMutate: (args: [number, any]) => Promise<any>;
|
||||
createCustomerMutate: (values: any) => Promise<any>;
|
||||
};
|
||||
|
||||
type CustomerFormProviderProps = {
|
||||
query?: unknown;
|
||||
customerId?: number;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const CustomerFormContext = createContext<CustomerFormContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function CustomerFormProvider({ query, customerId, children }: CustomerFormProviderProps) {
|
||||
const { state } = useLocation<{ action?: number | string }>();
|
||||
const contactId = state?.action;
|
||||
|
||||
// Features guard.
|
||||
@@ -33,7 +82,7 @@ function CustomerFormProvider({ query, customerId, ...props }) {
|
||||
{ enabled: !!contactId },
|
||||
);
|
||||
// Handle fetch Currencies data table
|
||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies(undefined);
|
||||
|
||||
// Fetches the branches list.
|
||||
const {
|
||||
@@ -43,23 +92,26 @@ function CustomerFormProvider({ query, customerId, ...props }) {
|
||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||
|
||||
// Form submit payload.
|
||||
const [submitPayload, setSubmitPayload] = useState({});
|
||||
const [submitPayload, setSubmitPayload] = useState<CustomerFormSubmitPayload>({});
|
||||
|
||||
const { mutateAsync: editCustomerMutate } = useEditCustomer();
|
||||
const { mutateAsync: createCustomerMutate } = useCreateCustomer();
|
||||
const editCustomerMutation = useEditCustomer(undefined) as any;
|
||||
const createCustomerMutation = useCreateCustomer(undefined) as any;
|
||||
const editCustomerMutate = editCustomerMutation.mutateAsync as CustomerFormContextValue['editCustomerMutate'];
|
||||
const createCustomerMutate =
|
||||
createCustomerMutation.mutateAsync as CustomerFormContextValue['createCustomerMutate'];
|
||||
|
||||
// determines whether the form new or duplicate mode.
|
||||
const isNewMode = contactId || !customerId;
|
||||
const isNewMode = Boolean(contactId) || !customerId;
|
||||
|
||||
const isFormLoading =
|
||||
isCustomerLoading || isCurrenciesLoading || isBranchesLoading;
|
||||
|
||||
const provider = {
|
||||
const provider: CustomerFormContextValue = {
|
||||
customerId,
|
||||
customer,
|
||||
currencies,
|
||||
branches,
|
||||
contactDuplicate,
|
||||
customer: customer as Customer | undefined,
|
||||
currencies: (currencies as Currency[]) ?? [],
|
||||
branches: (branches as Branch[]) ?? [],
|
||||
contactDuplicate: contactDuplicate as Customer | undefined,
|
||||
submitPayload,
|
||||
isNewMode,
|
||||
|
||||
@@ -73,9 +125,19 @@ function CustomerFormProvider({ query, customerId, ...props }) {
|
||||
createCustomerMutate,
|
||||
};
|
||||
|
||||
return <CustomerFormContext.Provider value={provider} {...props} />;
|
||||
return (
|
||||
<CustomerFormContext.Provider value={provider}>
|
||||
{children}
|
||||
</CustomerFormContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
|
||||
|
||||
export { CustomerFormProvider, useCustomerFormContext };
|
||||
export const useCustomerFormContext = () => {
|
||||
const ctx = React.useContext(CustomerFormContext);
|
||||
if (!ctx) {
|
||||
throw new Error(
|
||||
'useCustomerFormContext must be used within a CustomerFormProvider',
|
||||
);
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
const customerFormSectionTitleClass = css`
|
||||
font-size: 14px;
|
||||
color: #8f99a8;
|
||||
margin-bottom: 18px;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
export function CustomerFormSectionTitle({ children }: { children: React.ReactNode | string }) {
|
||||
return <h4 className={customerFormSectionTitleClass}>{children}</h4>;
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components';
|
||||
|
||||
export default function CustomerNotePanel({ errors, touched, getFieldProps }) {
|
||||
return (
|
||||
<div className={'tab-panel--note'}>
|
||||
<FFormGroup name={'note'} label={<T id={'note'} />} inline={false}>
|
||||
<FTextArea name={'note'} />
|
||||
</FFormGroup>
|
||||
</div>
|
||||
<FFormGroup name={'note'} label={<T id={'note'} />} inline={false} fill>
|
||||
<FTextArea name={'note'} fill />
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Box } from '@/components';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||
|
||||
export function CustomerShippingAddress() {
|
||||
return (
|
||||
<Box data-section-id="shippingAddress">
|
||||
<CustomerFormSectionTitle>
|
||||
<T id={'shipping_address'} />
|
||||
</CustomerFormSectionTitle>
|
||||
<FFormGroup
|
||||
name={'shipping_address_country'}
|
||||
label={<T id={'country'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'shipping_address_country'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address1'}
|
||||
label={<T id={'address_line_1'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FTextArea name={'shipping_address1'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address2'}
|
||||
label={<T id={'address_line_2'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FTextArea name={'shipping_address2'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_city'}
|
||||
label={<T id={'city_town'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'shipping_address_city'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_state'}
|
||||
label={<T id={'state'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'shipping_address_state'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_postcode'}
|
||||
label={<T id={'zip_code'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'shipping_address_postcode'} fill />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_phone'}
|
||||
label={<T id={'phone'} />}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<FInputGroup name={'shipping_address_phone'} fill />
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +1,52 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { Radio } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, FFormGroup, FRadioGroup } from '@/components';
|
||||
|
||||
import { handleStringChange, saveInvoke } from '@/utils';
|
||||
import { Button, ButtonGroup } from '@blueprintjs/core';
|
||||
import { FastField } from 'formik';
|
||||
import { FormattedMessage as T, FFormGroup } from '@/components';
|
||||
|
||||
/**
|
||||
* Customer type radio field.
|
||||
* Customer type selector (button group).
|
||||
*/
|
||||
export default function RadioCustomer() {
|
||||
export function CustomerTypeRadioField() {
|
||||
return (
|
||||
<FFormGroup
|
||||
name={'customer_type'}
|
||||
label={<T id={'customer_type'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FRadioGroup name={'customer_type'} inline>
|
||||
<Radio label={intl.get('business')} value="business" />
|
||||
<Radio label={intl.get('individual')} value="individual" />
|
||||
</FRadioGroup>
|
||||
<FastField name="customer_type">
|
||||
{({ field, form }) => (
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
small
|
||||
active={field.value === 'business'}
|
||||
onClick={() => {
|
||||
form.setFieldValue('customer_type', 'business');
|
||||
form.setFieldTouched('customer_type', true);
|
||||
}}
|
||||
>
|
||||
{intl.get('business')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
small
|
||||
active={field.value === 'individual'}
|
||||
onClick={() => {
|
||||
form.setFieldValue('customer_type', 'individual');
|
||||
form.setFieldTouched('customer_type', true);
|
||||
}}
|
||||
>
|
||||
{intl.get('individual')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</FastField>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Tabs, Tab } from '@blueprintjs/core';
|
||||
|
||||
import CustomerAddressTabs from './CustomerAddressTabs';
|
||||
import CustomerAttachmentTabs from './CustomerAttachmentTabs';
|
||||
import CustomerFinancialPanel from './CustomerFinancialPanel';
|
||||
import CustomerFinancialPanel from './CustomerFormFinancialSection';
|
||||
import CustomerNotePanel from './CustomerNotePanel';
|
||||
|
||||
export default function CustomersTabs() {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const defaultInitialValues = {
|
||||
last_name: '',
|
||||
company_name: '',
|
||||
display_name: '',
|
||||
code: '',
|
||||
|
||||
email: '',
|
||||
work_phone: '',
|
||||
|
||||
+1
-23
@@ -8,9 +8,7 @@ import {
|
||||
CustomerFormProvider,
|
||||
useCustomerFormContext,
|
||||
} from '@/containers/Customers/CustomerForm/CustomerFormProvider';
|
||||
import CustomerFormFormik, {
|
||||
CustomerFormHeaderPrimary,
|
||||
} from '@/containers/Customers/CustomerForm/CustomerFormFormik';
|
||||
import { CustomerFormFormik } from '@/containers/Customers/CustomerForm/CustomerFormFormik';
|
||||
|
||||
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
@@ -55,34 +53,14 @@ function QuickCustomerFormDrawer({
|
||||
return (
|
||||
<CustomerFormProvider customerId={customerId}>
|
||||
<DrawerCustomerFormLoading>
|
||||
<CustomerFormCard>
|
||||
<CustomerFormFormik
|
||||
initialValues={{ first_name: displayName }}
|
||||
onSubmitSuccess={handleSubmitSuccess}
|
||||
onCancel={handleCancelForm}
|
||||
/>
|
||||
</CustomerFormCard>
|
||||
</DrawerCustomerFormLoading>
|
||||
</CustomerFormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDrawerActions)(QuickCustomerFormDrawer);
|
||||
|
||||
const CustomerFormCard = styled(Card)`
|
||||
margin: 15px;
|
||||
padding: 25px;
|
||||
margin-bottom: calc(15px + 65px);
|
||||
|
||||
${CustomerFormHeaderPrimary} {
|
||||
padding-top: 0;
|
||||
}
|
||||
.page-form {
|
||||
padding: 0;
|
||||
|
||||
&__floating-actions {
|
||||
margin-left: -41px;
|
||||
margin-right: -41px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
+2
-21
@@ -8,8 +8,8 @@ import {
|
||||
VendorFormProvider,
|
||||
useVendorFormContext,
|
||||
} from '@/containers/Vendors/VendorForm/VendorFormProvider';
|
||||
import VendorFormFormik, {
|
||||
VendorFormHeaderPrimary,
|
||||
import {
|
||||
VendorFormFormik,
|
||||
} from '@/containers/Vendors/VendorForm/VendorFormFormik';
|
||||
|
||||
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
||||
@@ -62,13 +62,11 @@ function QuickVendorFormDrawer({
|
||||
return (
|
||||
<VendorFormProvider vendorId={vendorId}>
|
||||
<DrawerVendorFormLoading>
|
||||
<VendorFormCard>
|
||||
<VendorFormFormik
|
||||
initialValues={{ first_name: displayName }}
|
||||
onSubmitSuccess={handleSubmitSuccess}
|
||||
onCancel={handleCancelForm}
|
||||
/>
|
||||
</VendorFormCard>
|
||||
</DrawerVendorFormLoading>
|
||||
</VendorFormProvider>
|
||||
);
|
||||
@@ -79,20 +77,3 @@ export default R.compose(
|
||||
withDashboardActions,
|
||||
)(QuickVendorFormDrawer);
|
||||
|
||||
const VendorFormCard = styled(Card)`
|
||||
margin: 15px;
|
||||
padding: 25px;
|
||||
margin-bottom: calc(15px + 65px);
|
||||
|
||||
${VendorFormHeaderPrimary} {
|
||||
padding-top: 0;
|
||||
}
|
||||
.page-form {
|
||||
padding: 0;
|
||||
|
||||
&__floating-actions {
|
||||
margin-left: -41px;
|
||||
margin-right: -41px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
-1
@@ -18,7 +18,6 @@ export default function QuickWriteVendorDrawerContent({ displayName, autofillRef
|
||||
<DrawerHeaderContent
|
||||
name={DRAWERS.QUICK_CREATE_CUSTOMER}
|
||||
title={<T id={'create_a_new_vendor'} />}
|
||||
|
||||
/>
|
||||
<DrawerBody>
|
||||
<QuickVendorFormDrawer displayName={displayName} autofillRef={autofillRef} />
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Dragzone, FormattedMessage as T } from '@/components';
|
||||
/**
|
||||
* Vendor Attachment Tab.
|
||||
*/
|
||||
function VendorAttachmentTab() {
|
||||
export function VendorAttachmentTab() {
|
||||
return (
|
||||
<div>
|
||||
<Dragzone
|
||||
@@ -17,5 +17,3 @@ function VendorAttachmentTab() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VendorAttachmentTab;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// @ts-nocheck
|
||||
import { Box } from '@/components';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||
|
||||
export function VendorBillingAddress() {
|
||||
return (
|
||||
<Box data-section-id="billingAddress">
|
||||
<VendorFormSectionTitle>
|
||||
<T id={'billing_address'} />
|
||||
</VendorFormSectionTitle>
|
||||
<FFormGroup
|
||||
name={'billing_address_country'}
|
||||
label={<T id={'country'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'billing_address_country'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address1'}
|
||||
label={<T id={'address_line_1'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FTextArea name={'billing_address1'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address2'}
|
||||
label={<T id={'address_line_2'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FTextArea name={'billing_address2'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_city'}
|
||||
label={<T id={'city_town'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'billing_address_city'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_state'}
|
||||
label={<T id={'state'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'billing_address_state'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_postcode'}
|
||||
label={<T id={'zip_code'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'billing_address_postcode'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'billing_address_phone'}
|
||||
label={<T id={'phone'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'billing_address_phone'} fill fastField />
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import { useCurrentOrganization } from '@/hooks/state';
|
||||
/**
|
||||
* Vendor Finaniceal Panel Tab.
|
||||
*/
|
||||
export default function VendorFinanicalPanelTab() {
|
||||
export function VendorFinanicalPanelTab() {
|
||||
const { currencies, branches } = useVendorFormContext();
|
||||
|
||||
// Sets the primary branch to form.
|
||||
@@ -44,10 +44,12 @@ export default function VendorFinanicalPanelTab() {
|
||||
label={<T id={'currency'} />}
|
||||
fastField
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<CurrencySelectList
|
||||
name="currency_code"
|
||||
items={currencies}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
@@ -93,16 +95,17 @@ function VendorOpeningBalanceField() {
|
||||
<FFormGroup
|
||||
name={'opening_balance'}
|
||||
label={<T id={'opening_balance'} />}
|
||||
inline={true}
|
||||
shouldUpdate={openingBalanceFieldShouldUpdate}
|
||||
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
||||
fastField={true}
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<ControlGroup>
|
||||
<InputPrependText text={values.currency_code} />
|
||||
<FMoneyInputGroup
|
||||
name={'opening_balance'}
|
||||
inputGroupProps={{ fill: true }}
|
||||
fastField
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
@@ -123,8 +126,9 @@ function VendorOpeningBalanceAtField() {
|
||||
<FFormGroup
|
||||
name={'opening_balance_at'}
|
||||
label={<T id={'opening_balance_at'} />}
|
||||
inline={true}
|
||||
helperText={<ErrorMessage name="opening_balance_at" />}
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<FDateInput
|
||||
name={'opening_balance_at'}
|
||||
@@ -132,7 +136,8 @@ function VendorOpeningBalanceAtField() {
|
||||
disabled={vendorId}
|
||||
formatDate={(date) => date.toLocaleDateString()}
|
||||
parseDate={(str) => new Date(str)}
|
||||
fill={true}
|
||||
fill
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
);
|
||||
@@ -156,12 +161,14 @@ function VendorOpeningBalanceExchangeRateField() {
|
||||
<FFormGroup
|
||||
label={' '}
|
||||
name={'opening_balance_exchange_rate'}
|
||||
inline={true}
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<ExchangeRateInputGroup
|
||||
fromCurrency={values.currency_code}
|
||||
toCurrency={currentOrganization.base_currency}
|
||||
name={'opening_balance_exchange_rate'}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import {
|
||||
Intent,
|
||||
Button,
|
||||
@@ -11,53 +10,37 @@ import {
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
import classNames from 'classnames';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useVendorFormContext } from './VendorFormProvider';
|
||||
import { safeInvoke } from '@/utils';
|
||||
|
||||
/**
|
||||
* Vendor floating actions bar.
|
||||
*/
|
||||
export default function VendorFloatingActions({ onCancel }) {
|
||||
export function VendorFloatingActions() {
|
||||
// Formik context.
|
||||
const { resetForm, isSubmitting, submitForm } = useFormikContext();
|
||||
const { isSubmitting, submitForm } = useFormikContext();
|
||||
|
||||
// Vendor form context.
|
||||
const { isNewMode, setSubmitPayload } = useVendorFormContext();
|
||||
|
||||
// Handle the submit button.
|
||||
const handleSubmitBtnClick = (event) => {
|
||||
const handleSubmitBtnClick = () => {
|
||||
setSubmitPayload({ noRedirect: false });
|
||||
};
|
||||
|
||||
// Handle the submit & new button click.
|
||||
const handleSubmitAndNewClick = (event) => {
|
||||
const handleSubmitAndNewClick = () => {
|
||||
submitForm();
|
||||
setSubmitPayload({ noRedirect: true });
|
||||
};
|
||||
|
||||
// Handle cancel button click.
|
||||
const handleCancelBtnClick = (event) => {
|
||||
safeInvoke(onCancel, event);
|
||||
};
|
||||
|
||||
// Handle clear button click.
|
||||
const handleClearBtnClick = (event) => {
|
||||
resetForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
<FloatingActionsGroup spacing={10}>
|
||||
<ButtonGroup>
|
||||
{/* ----------- Save and New ----------- */}
|
||||
<SaveButton
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
@@ -74,9 +57,9 @@ export default function VendorFloatingActions({ onCancel }) {
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
minimal
|
||||
>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
@@ -85,24 +68,16 @@ export default function VendorFloatingActions({ onCancel }) {
|
||||
/>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
{/* ----------- Clear & Reset----------- */}
|
||||
<Button
|
||||
className={'ml1'}
|
||||
disabled={isSubmitting}
|
||||
onClick={handleClearBtnClick}
|
||||
text={!isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
|
||||
/>
|
||||
{/* ----------- Cancel ----------- */}
|
||||
<Button
|
||||
className={'ml1'}
|
||||
disabled={isSubmitting}
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</Group>
|
||||
</FloatingActionsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const SaveButton = styled(Button)`
|
||||
min-width: 100px;
|
||||
`;
|
||||
const FloatingActionsGroup = styled(Group)`
|
||||
padding: 10px 0;
|
||||
padding-left: 165px;
|
||||
border-top: 1px solid #50555a;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: var(--color-card-background);
|
||||
z-index: 1;
|
||||
`;
|
||||
+13
-12
@@ -2,21 +2,22 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { ControlGroup } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, FFormGroup, FInputGroup } from '@/components';
|
||||
import { FormattedMessage as T, FFormGroup, FInputGroup, Box } from '@/components';
|
||||
|
||||
/**
|
||||
* Vendor form after primary section.
|
||||
*/
|
||||
function VendorFormAfterPrimarySection() {
|
||||
export function VendorFormAfterPrimarySection() {
|
||||
return (
|
||||
<div className={'customer-form__after-primary-section-content'}>
|
||||
<Box>
|
||||
{/*------------ Vendor email -----------*/}
|
||||
<FFormGroup
|
||||
name={'email'}
|
||||
label={<T id={'vendor_email'} />}
|
||||
inline={true}
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'email'} />
|
||||
<FInputGroup name={'email'} fastField />
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Phone number -----------*/}
|
||||
@@ -24,23 +25,23 @@ function VendorFormAfterPrimarySection() {
|
||||
name={'work_phone'}
|
||||
className={'form-group--phone-number'}
|
||||
label={<T id={'phone_number'} />}
|
||||
inline={true}
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<ControlGroup>
|
||||
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} />
|
||||
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} fastField />
|
||||
<FInputGroup
|
||||
name={'personal_phone'}
|
||||
placeholder={intl.get('mobile')}
|
||||
fastField
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Vendor website -----------*/}
|
||||
<FFormGroup name={'website'} label={<T id={'website'} />} inline={true}>
|
||||
<FInputGroup name={'website'} placeholder={'http://'} />
|
||||
<FFormGroup name={'website'} label={<T id={'website'} />} inline fastField>
|
||||
<FInputGroup name={'website'} placeholder={'http://'} fastField />
|
||||
</FFormGroup>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default VendorFormAfterPrimarySection;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// @ts-nocheck
|
||||
import intl from 'react-intl-universal';
|
||||
import { ControlGroup, Divider, Icon as BlueprintIcon } from '@blueprintjs/core';
|
||||
import {
|
||||
Hint,
|
||||
FieldRequiredHint,
|
||||
SalutationList,
|
||||
DisplayNameList,
|
||||
FormattedMessage as T,
|
||||
FInputGroup,
|
||||
FFormGroup,
|
||||
Box,
|
||||
Icon,
|
||||
Stack,
|
||||
} from '@/components';
|
||||
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||
import { useAutofocus } from '@/hooks';
|
||||
|
||||
export function VendorFormBasicSection({}) {
|
||||
const firstNameFieldRef = useAutofocus();
|
||||
|
||||
return (
|
||||
<Box data-section-id="primary">
|
||||
<VendorFormSectionTitle>Vendor details</VendorFormSectionTitle>
|
||||
|
||||
{/**----------- Contact name -----------*/}
|
||||
<FFormGroup
|
||||
name={'salutation'}
|
||||
label={<T id={'contact_name'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<ControlGroup fill>
|
||||
<SalutationList
|
||||
name={'salutation'}
|
||||
popoverProps={{ minimal: true }}
|
||||
fastField
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'first_name'}
|
||||
placeholder={intl.get('first_name')}
|
||||
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
||||
fill
|
||||
fastField
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'last_name'}
|
||||
placeholder={intl.get('last_name')}
|
||||
fill
|
||||
fastField
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'code'}
|
||||
label={'Vendor Code'}
|
||||
helperText="Add a unique account number to identify, reference and search for the contact."
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'code'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Company Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'company_name'}
|
||||
label={<T id={'company_name'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'company_name'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Display Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'display_name'}
|
||||
label={<T id={'display_name'} />}
|
||||
helperText="This is the name that appears on invoices and emails."
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<DisplayNameList
|
||||
name={'display_name'}
|
||||
popoverProps={{ minimal: true }}
|
||||
buttonProps={{ fill: true }}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<Divider style={{ margin: '20px 0' }} />
|
||||
|
||||
{/*------------ Vendor email -----------*/}
|
||||
<FFormGroup
|
||||
name={'email'}
|
||||
label={<T id={'vendor_email'} />}
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<FInputGroup
|
||||
name={'email'}
|
||||
leftIcon={<Icon icon="envelope" />}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Phone number -----------*/}
|
||||
<FFormGroup
|
||||
name={'work_phone'}
|
||||
className={'form-group--phone-number'}
|
||||
label={<T id={'phone_number'} />}
|
||||
inline
|
||||
fastField
|
||||
>
|
||||
<Stack spacing={10}>
|
||||
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} leftIcon="phone" fastField
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'personal_phone'}
|
||||
placeholder={intl.get('mobile')}
|
||||
fastField
|
||||
/>
|
||||
</Stack>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Vendor website -----------*/}
|
||||
<FFormGroup name={'website'} label={<T id={'website'} />} inline fastField>
|
||||
<FInputGroup
|
||||
name={'website'}
|
||||
placeholder={'http://'}
|
||||
leftIcon={<BlueprintIcon icon="globe-network" />}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// @ts-nocheck
|
||||
import { Tab } from "@blueprintjs/core";
|
||||
import { Card, Group } from "@/components";
|
||||
import { Tabs } from "@blueprintjs/core";
|
||||
import { useState } from "react";
|
||||
import { css } from '@emotion/css';
|
||||
import { VendorFloatingActions } from "./VendorFloatingActions";
|
||||
import { VendorFormSections } from "./VendorFormFields";
|
||||
|
||||
export function VendorFormContent() {
|
||||
const [selectedTabId, setSelectedTabId] = useState('primary');
|
||||
|
||||
const handleTabChange = (tabId: string) => {
|
||||
const sectionId = String(tabId);
|
||||
setSelectedTabId(sectionId);
|
||||
|
||||
const section = document.querySelector(
|
||||
`[data-section-id="${sectionId}"]`,
|
||||
);
|
||||
if (section) {
|
||||
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={css`padding-bottom: 0 !important;`}>
|
||||
<Group verticalAlign={'top'} alignItems={'flex-start'} flexWrap={'nowrap'}>
|
||||
<Tabs
|
||||
selectedTabId={selectedTabId}
|
||||
onChange={handleTabChange}
|
||||
className={css`position: sticky; top: 20px;`}
|
||||
vertical
|
||||
>
|
||||
<Tab id={'primary'} title={'Basic'} />
|
||||
<Tab id={'financial'} title={'Financial'} />
|
||||
<Tab id={'billingAddress'} title={'Billing address'} />
|
||||
<Tab id={'shippingAddress'} title={'Shipping address'} />
|
||||
<Tab id={'notes'} title={'Notes'} />
|
||||
</Tabs>
|
||||
<VendorFormSections />
|
||||
</Group>
|
||||
<VendorFloatingActions />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// @ts-nocheck
|
||||
import { Divider } from '@blueprintjs/core';
|
||||
import { css } from '@emotion/css';
|
||||
import { Box } from '@/components';
|
||||
|
||||
import { VendorFormBasicSection } from './VendorFormBasicSection';
|
||||
import { VendorFormFinancialSection } from './VendorFormFinancialSection';
|
||||
import { VendorBillingAddress } from './VendorBillingAddress';
|
||||
import { VendorShippingAddress } from './VendorShippingAddress';
|
||||
import { VendorFormNotesSection } from './VendorFormNotesSection';
|
||||
|
||||
const vendorFormSectionDividerClass = css`
|
||||
margin: 20px 0;
|
||||
`;
|
||||
|
||||
export function VendorFormSections() {
|
||||
return (
|
||||
<Box>
|
||||
<VendorFormBasicSection />
|
||||
<Divider className={vendorFormSectionDividerClass} />
|
||||
|
||||
<VendorFormFinancialSection />
|
||||
<Divider className={vendorFormSectionDividerClass} />
|
||||
|
||||
<VendorBillingAddress />
|
||||
<Divider className={vendorFormSectionDividerClass} />
|
||||
|
||||
<VendorShippingAddress />
|
||||
<Divider className={vendorFormSectionDividerClass} />
|
||||
|
||||
<VendorFormNotesSection />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// @ts-nocheck
|
||||
import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
|
||||
import { ErrorMessage, useFormikContext } from 'formik';
|
||||
import { Features } from '@/constants';
|
||||
import {
|
||||
FFormGroup,
|
||||
FormattedMessage as T,
|
||||
InputPrependText,
|
||||
CurrencySelectList,
|
||||
BranchSelect,
|
||||
FeatureCan,
|
||||
FMoneyInputGroup,
|
||||
ExchangeRateInputGroup,
|
||||
FDateInput,
|
||||
Icon,
|
||||
Box,
|
||||
} from '@/components';
|
||||
import { useVendorFormContext } from './VendorFormProvider';
|
||||
import {
|
||||
openingBalanceFieldShouldUpdate,
|
||||
useIsVendorForeignCurrency,
|
||||
useSetPrimaryBranchToForm,
|
||||
} from './utils';
|
||||
import { useCurrentOrganization } from '@/hooks/state';
|
||||
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||
|
||||
export function VendorFormFinancialSection() {
|
||||
const { currencies, vendorId, branches } = useVendorFormContext();
|
||||
|
||||
// Sets the primary branch to form.
|
||||
useSetPrimaryBranchToForm();
|
||||
|
||||
return (
|
||||
<Box data-section-id="financial">
|
||||
<VendorFormSectionTitle>
|
||||
<T id={'financial_details'} />
|
||||
</VendorFormSectionTitle>
|
||||
|
||||
<FFormGroup
|
||||
name={'currency_code'}
|
||||
label={<T id={'currency'} />}
|
||||
fastField
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<CurrencySelectList
|
||||
name="currency_code"
|
||||
items={currencies}
|
||||
disabled={vendorId}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<VendorOpeningBalanceField />
|
||||
<VendorOpeningBalanceExchangeRateField />
|
||||
<VendorOpeningBalanceAtField />
|
||||
|
||||
<FeatureCan feature={Features.Branches}>
|
||||
<FFormGroup
|
||||
label={<T id={'vendor.label.opening_branch'} />}
|
||||
name={'opening_balance_branch_id'}
|
||||
inline
|
||||
fill
|
||||
>
|
||||
<BranchSelect
|
||||
name={'opening_balance_branch_id'}
|
||||
branches={branches}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
</FeatureCan>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor opening balance at date field.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function VendorOpeningBalanceAtField() {
|
||||
const { vendorId } = useVendorFormContext();
|
||||
|
||||
// Cannot continue if the vendor id is defined.
|
||||
if (vendorId) return null;
|
||||
|
||||
return (
|
||||
<FFormGroup
|
||||
name={'opening_balance_at'}
|
||||
label={<T id={'opening_balance_at'} />}
|
||||
inline
|
||||
fill
|
||||
helperText={<ErrorMessage name="opening_balance_at" />}
|
||||
>
|
||||
<FDateInput
|
||||
name={'opening_balance_at'}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
disabled={vendorId}
|
||||
formatDate={(date) => date.toLocaleDateString()}
|
||||
parseDate={(str) => new Date(str)}
|
||||
inputProps={{
|
||||
leftIcon: <Icon icon={'date-range'} />,
|
||||
}}
|
||||
fill={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function VendorOpeningBalanceField() {
|
||||
const { vendorId } = useVendorFormContext();
|
||||
const { values } = useFormikContext();
|
||||
|
||||
// Cannot continue if the vendor id is defined.
|
||||
if (vendorId) return null;
|
||||
|
||||
return (
|
||||
<FFormGroup
|
||||
label={<T id={'opening_balance'} />}
|
||||
name={'opening_balance'}
|
||||
inline
|
||||
shouldUpdate={openingBalanceFieldShouldUpdate}
|
||||
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
||||
fastField={true}
|
||||
fill
|
||||
>
|
||||
<ControlGroup fill>
|
||||
<InputPrependText text={values.currency_code as string} />
|
||||
<FMoneyInputGroup
|
||||
name={'opening_balance'}
|
||||
fastField
|
||||
inputGroupProps={{ fill: true }}
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function VendorOpeningBalanceExchangeRateField() {
|
||||
const { values } = useFormikContext();
|
||||
const { vendorId } = useVendorFormContext();
|
||||
const currentOrganization = useCurrentOrganization();
|
||||
|
||||
const isForeignVendor = useIsVendorForeignCurrency();
|
||||
|
||||
// Can't continue if the vendor is not foreign.
|
||||
if (!isForeignVendor || vendorId) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ExchangeRateInputGroup
|
||||
fromCurrency={values.currency_code}
|
||||
toCurrency={currentOrganization.base_currency}
|
||||
name={'opening_balance_exchange_rate'}
|
||||
onRecalcConfirm={() => {}}
|
||||
onCancel={() => {}}
|
||||
formGroupProps={{ label: ' ' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Formik, Form } from 'formik';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
@@ -7,16 +7,13 @@ import classNames from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { AppToaster } from '@/components';
|
||||
import { AppToaster, Box } from '@/components';
|
||||
import {
|
||||
CreateVendorFormSchema,
|
||||
EditVendorFormSchema,
|
||||
} from './VendorForm.schema';
|
||||
|
||||
import VendorTabs from './VendorsTabs';
|
||||
import VendorFormPrimarySection from './VendorFormPrimarySection';
|
||||
import VendorFormAfterPrimarySection from './VendorFormAfterPrimarySection';
|
||||
import VendorFloatingActions from './VendorFloatingActions';
|
||||
import { VendorFormContent } from './VendorFormContent';
|
||||
|
||||
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
||||
|
||||
@@ -24,12 +21,10 @@ import { useVendorFormContext } from './VendorFormProvider';
|
||||
import { compose, transformToForm, safeInvoke, parseBoolean } from '@/utils';
|
||||
import { defaultInitialValues } from './utils';
|
||||
|
||||
import '@/style/pages/Vendors/Form.scss';
|
||||
|
||||
/**
|
||||
* Vendor form.
|
||||
*/
|
||||
function VendorFormFormik({
|
||||
function VendorFormFormikBase({
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
|
||||
@@ -52,9 +47,6 @@ function VendorFormFormik({
|
||||
isNewMode,
|
||||
} = useVendorFormContext();
|
||||
|
||||
/**
|
||||
* Initial values in create and edit mode.
|
||||
*/
|
||||
const initialFormValues = useMemo(
|
||||
() => ({
|
||||
...defaultInitialValues,
|
||||
@@ -106,51 +98,34 @@ function VendorFormFormik({
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
CLASSES.PAGE_FORM,
|
||||
CLASSES.PAGE_FORM_VENDOR,
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Formik
|
||||
validationSchema={
|
||||
isNewMode ? CreateVendorFormSchema : EditVendorFormSchema
|
||||
}
|
||||
initialValues={initialFormValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
>
|
||||
<Form>
|
||||
<VendorFormHeaderPrimary>
|
||||
<VendorFormPrimarySection />
|
||||
</VendorFormHeaderPrimary>
|
||||
|
||||
<div className={'page-form__after-priamry-section'}>
|
||||
<VendorFormAfterPrimarySection />
|
||||
</div>
|
||||
|
||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
||||
<VendorTabs vendor={vendorId} />
|
||||
</div>
|
||||
|
||||
<VendorFloatingActions onCancel={onCancel} />
|
||||
<VendorFormFields>
|
||||
<VendorFormContent onCancel={onCancel} />
|
||||
</VendorFormFields>
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export const VendorFormHeaderPrimary = styled.div`
|
||||
--x-color-border: #e4e4e4;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-color-border: var(--color-dark-gray3);
|
||||
const VendorFormFields = styled.div`
|
||||
.bp4-form-content,
|
||||
.bp6-form-content {
|
||||
min-width: 300px;
|
||||
}
|
||||
.bp4-form-group{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.bp4-form-group.bp4-inline label.bp4-label {
|
||||
min-width: 140px;
|
||||
}
|
||||
padding: 10px 0 0;
|
||||
margin: 0 0 20px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid var(--x-color-border);
|
||||
max-width: 1000px;
|
||||
`;
|
||||
|
||||
export default compose(withCurrentOrganization())(VendorFormFormik);
|
||||
export const VendorFormFormik = compose(withCurrentOrganization())(VendorFormFormikBase);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import { Box, FFormGroup, FormattedMessage as T, FTextArea } from '@/components';
|
||||
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||
|
||||
export function VendorFormNotesSection() {
|
||||
return (
|
||||
<Box data-section-id="notes">
|
||||
<VendorFormSectionTitle>
|
||||
<T id={'notes'} />
|
||||
</VendorFormSectionTitle>
|
||||
|
||||
<FFormGroup name={'note'} label={<T id={'note'} />} inline fill fastField>
|
||||
<FTextArea name={'note'} fill fastField />
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -2,12 +2,9 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
|
||||
import '@/style/pages/Vendors/PageForm.scss';
|
||||
|
||||
import { DashboardCard, DashboardInsider } from '@/components';
|
||||
import { Box, DashboardCard, DashboardInsider } from '@/components';
|
||||
import { VendorFormProvider, useVendorFormContext } from './VendorFormProvider';
|
||||
import VendorFormFormik from './VendorFormFormik';
|
||||
import { VendorFormFormik } from './VendorFormFormik';
|
||||
|
||||
/**
|
||||
* Vendor form page loading wrapper.
|
||||
@@ -17,16 +14,16 @@ function VendorFormPageLoading({ children }) {
|
||||
const { isFormLoading } = useVendorFormContext();
|
||||
|
||||
return (
|
||||
<VendorDashboardInsider loading={isFormLoading}>
|
||||
<DashboardInsider loading={isFormLoading}>
|
||||
{children}
|
||||
</VendorDashboardInsider>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor form page.
|
||||
*/
|
||||
export default function VendorFormPage() {
|
||||
export function VendorFormPage() {
|
||||
const history = useHistory();
|
||||
const { id } = useParams();
|
||||
|
||||
@@ -44,26 +41,13 @@ export default function VendorFormPage() {
|
||||
return (
|
||||
<VendorFormProvider vendorId={id}>
|
||||
<VendorFormPageLoading>
|
||||
<DashboardCard page>
|
||||
<VendorFormPageFormik
|
||||
<Box mx={'auto'} maxWidth={800}>
|
||||
<VendorFormFormik
|
||||
onSubmitSuccess={handleSubmitSuccess}
|
||||
onCancel={handleFormCancel}
|
||||
/>
|
||||
</DashboardCard>
|
||||
/>
|
||||
</Box>
|
||||
</VendorFormPageLoading>
|
||||
</VendorFormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const VendorFormPageFormik = styled(VendorFormFormik)`
|
||||
.page-form {
|
||||
&__floating-actions {
|
||||
margin-left: -40px;
|
||||
margin-right: -40px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const VendorDashboardInsider = styled(DashboardInsider)`
|
||||
padding-bottom: 64px;
|
||||
`;
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { ControlGroup } from '@blueprintjs/core';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
Hint,
|
||||
FieldRequiredHint,
|
||||
SalutationList,
|
||||
DisplayNameList,
|
||||
} from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useAutofocus } from '@/hooks';
|
||||
|
||||
/**
|
||||
* Vendor form primary section.
|
||||
*/
|
||||
function VendorFormPrimarySection() {
|
||||
const firstNameFieldRef = useAutofocus();
|
||||
|
||||
return (
|
||||
<div className={'customer-form__primary-section-content'}>
|
||||
{/**----------- Vendor name -----------*/}
|
||||
<FFormGroup
|
||||
name={'salutation'}
|
||||
className={classNames('form-group--contact_name')}
|
||||
label={<T id={'contact_name'} />}
|
||||
inline={true}
|
||||
>
|
||||
<ControlGroup>
|
||||
<SalutationList
|
||||
name={'salutation'}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'first_name'}
|
||||
placeholder={intl.get('first_name')}
|
||||
className={classNames('input-group--first-name')}
|
||||
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
||||
/>
|
||||
<FInputGroup
|
||||
name={'last_name'}
|
||||
placeholder={intl.get('last_name')}
|
||||
className={classNames('input-group--last-name')}
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Company Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'company_name'}
|
||||
className={classNames('form-group--company_name')}
|
||||
label={<T id={'company_name'} />}
|
||||
inline={true}
|
||||
>
|
||||
<FInputGroup name={'company_name'} />
|
||||
</FFormGroup>
|
||||
|
||||
{/*----------- Display Name -----------*/}
|
||||
<FFormGroup
|
||||
name={'display_name'}
|
||||
label={
|
||||
<>
|
||||
<T id={'display_name'} />
|
||||
<FieldRequiredHint />
|
||||
<Hint />
|
||||
</>
|
||||
}
|
||||
fastField
|
||||
inline
|
||||
>
|
||||
<DisplayNameList
|
||||
name={'display_name'}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VendorFormPrimarySection;
|
||||
@@ -33,7 +33,6 @@ function VendorFormProvider({ query, vendorId, ...props }) {
|
||||
const { data: vendor, isLoading: isVendorLoading } = useVendor(vendorId, {
|
||||
enabled: !!vendorId,
|
||||
});
|
||||
|
||||
// Handle fetch contact duplicate details.
|
||||
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
||||
contactId,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
const vendorFormSectionTitleClass = css`
|
||||
font-size: 14px;
|
||||
color: #8f99a8;
|
||||
margin-bottom: 18px;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
export function VendorFormSectionTitle({ children }: { children: React.ReactNode | string }) {
|
||||
return <h4 className={vendorFormSectionTitleClass}>{children}</h4>;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// @ts-nocheck
|
||||
import { Box } from '@/components';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||
|
||||
export function VendorShippingAddress() {
|
||||
return (
|
||||
<Box data-section-id="shippingAddress">
|
||||
<VendorFormSectionTitle>
|
||||
<T id={'shipping_address'} />
|
||||
</VendorFormSectionTitle>
|
||||
<FFormGroup
|
||||
name={'shipping_address_country'}
|
||||
label={<T id={'country'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'shipping_address_country'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address1'}
|
||||
label={<T id={'address_line_1'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FTextArea name={'shipping_address1'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address2'}
|
||||
label={<T id={'address_line_2'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FTextArea name={'shipping_address2'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_city'}
|
||||
label={<T id={'city_town'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'shipping_address_city'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_state'}
|
||||
label={<T id={'state'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'shipping_address_state'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_postcode'}
|
||||
label={<T id={'zip_code'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'shipping_address_postcode'} fill fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'shipping_address_phone'}
|
||||
label={<T id={'phone'} />}
|
||||
inline
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FInputGroup name={'shipping_address_phone'} fill fastField />
|
||||
</FFormGroup>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
import VendorFinanicalPanelTab from './VendorFinanicalPanelTab';
|
||||
|
||||
import CustomerAddressTabs from '@/containers/Customers/CustomerForm/CustomerAddressTabs';
|
||||
import CustomerNotePanel from '@/containers/Customers/CustomerForm/CustomerNotePanel';
|
||||
|
||||
/**
|
||||
* Vendor form tabs.
|
||||
*/
|
||||
export default function VendorTabs() {
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
||||
<Tabs
|
||||
animate={true}
|
||||
id={'vendor-tabs'}
|
||||
large={true}
|
||||
defaultSelectedTabId="financial"
|
||||
>
|
||||
<Tab
|
||||
id={'financial'}
|
||||
title={intl.get('financial_details')}
|
||||
panel={<VendorFinanicalPanelTab />}
|
||||
/>
|
||||
<Tab
|
||||
id={'address'}
|
||||
title={intl.get('address')}
|
||||
panel={<CustomerAddressTabs />}
|
||||
/>
|
||||
<Tab
|
||||
id="notes"
|
||||
title={intl.get('notes')}
|
||||
panel={<CustomerNotePanel />}
|
||||
/>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export const defaultInitialValues = {
|
||||
last_name: '',
|
||||
company_name: '',
|
||||
display_name: '',
|
||||
code: '',
|
||||
|
||||
email: '',
|
||||
work_phone: '',
|
||||
|
||||
@@ -1942,8 +1942,8 @@
|
||||
"vendor_opening_balance.label": "Edit Vendor Opening Balance",
|
||||
"vendor_opening_balance.label.opening_balance": "Opening balance",
|
||||
"vendor_opening_balance.label.opening_balance_at": "Opening balance at",
|
||||
"customer.label.opening_branch": "Opening Balance Branch",
|
||||
"vendor.label.opening_branch": "Opening Balance Branch",
|
||||
"customer.label.opening_branch": "Balance Branch",
|
||||
"vendor.label.opening_branch": "Balance Branch",
|
||||
"warehouse.error.warehouse_code_not_unique": "Warehouse code not unique",
|
||||
"warehouse.error.warehouse_has_associated_transactions": "You could not delete the warehouse that has associated transactions.",
|
||||
"branche.error.warehouse_code_not_unique": "Branch code not unique",
|
||||
|
||||
@@ -619,7 +619,7 @@ export const getDashboardRoutes = () => [
|
||||
{
|
||||
path: `/vendors/:id/edit`,
|
||||
component: lazy(
|
||||
() => import('@/containers/Vendors/VendorForm/VendorFormPage'),
|
||||
() => import('@/containers/Vendors/VendorForm/VendorFormPage').then(module => ({ default: module.VendorFormPage })),
|
||||
),
|
||||
name: 'vendor-edit',
|
||||
breadcrumb: intl.get('edit_vendor'),
|
||||
@@ -631,7 +631,7 @@ export const getDashboardRoutes = () => [
|
||||
{
|
||||
path: `/vendors/new`,
|
||||
component: lazy(
|
||||
() => import('@/containers/Vendors/VendorForm/VendorFormPage'),
|
||||
() => import('@/containers/Vendors/VendorForm/VendorFormPage').then(module => ({ default: module.VendorFormPage })),
|
||||
),
|
||||
name: 'vendor-new',
|
||||
breadcrumb: intl.get('new_vendor'),
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
min-height: 32px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
|
||||
&.bp4-outlined {
|
||||
.bp4-dark & {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-button:not([class*='bp4-intent-']) {
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
@import '../../_base.scss';
|
||||
|
||||
.page-form--customer {
|
||||
$self: '.page-form';
|
||||
padding: 20px;
|
||||
|
||||
--x-color-tabs-border: #f0f0f0;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-color-tabs-border: var(--color-dark-gray3);
|
||||
}
|
||||
|
||||
#{$self}__header {
|
||||
padding: 0;
|
||||
}
|
||||
#{$self}__primary-section {
|
||||
padding: 10px 0 0;
|
||||
margin: 0 0 20px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #e4e4e4;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.bp4-form-group {
|
||||
max-width: 500px;
|
||||
|
||||
.bp4-control {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&.bp4-inline {
|
||||
.bp4-label {
|
||||
min-width: 150px;
|
||||
}
|
||||
}
|
||||
.bp4-form-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group--contact_name {
|
||||
max-width: 600px;
|
||||
|
||||
.bp4-control-group > * {
|
||||
flex-shrink: unset;
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
&.input-group--salutation-list {
|
||||
width: 25%;
|
||||
}
|
||||
&.input-group--first-name,
|
||||
&.input-group--last-name {
|
||||
width: 37%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-form-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.bp4-tab-panel {
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.form-group--phone-number {
|
||||
.bp4-control-group > * {
|
||||
flex-shrink: unset;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#{$self}__tabs {
|
||||
margin-top: 20px;
|
||||
max-width: 1000px;
|
||||
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
color: #888;
|
||||
margin-bottom: 1.2rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
// Tab panels.
|
||||
.tab-panel {
|
||||
&--address {
|
||||
.bp4-form-group {
|
||||
max-width: 440px;
|
||||
|
||||
&.bp4-inline {
|
||||
.bp4-label {
|
||||
min-width: 145px;
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-form-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea.bp4-input {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
min-height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&--note {
|
||||
.form-group--note {
|
||||
.bp4-form-group {
|
||||
max-width: 600px;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropzone-container {
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-tabs {
|
||||
.bp4-tab-list {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--x-color-tabs-border);
|
||||
}
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 25px;
|
||||
}
|
||||
&.bp4-large > .bp4-tab {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user