1
0
This commit is contained in:
Ahmed Bouhuolia
2026-03-26 14:18:54 +02:00
parent 8f1af97fc0
commit 75699ba810
18 changed files with 382 additions and 252 deletions
@@ -12,7 +12,7 @@ import CustomerDetailsDrawer from '@/containers/Drawers/CustomerDetailsDrawer';
import VendorDetailsDrawer from '@/containers/Drawers/VendorDetailsDrawer';
import InventoryAdjustmentDetailDrawer from '@/containers/Drawers/InventoryAdjustmentDetailDrawer';
import CashflowTransactionDetailDrawer from '@/containers/Drawers/CashflowTransactionDetailDrawer';
import QuickCreateCustomerDrawer from '@/containers/Drawers/QuickCreateCustomerDrawer';
// import QuickCreateCustomerDrawer from '@/containers/Drawers/QuickCreateCustomerDrawer';
import QuickCreateItemDrawer from '@/containers/Drawers/QuickCreateItemDrawer';
import QuickWriteVendorDrawer from '@/containers/Drawers/QuickWriteVendorDrawer';
import CreditNoteDetailDrawer from '@/containers/Drawers/CreditNoteDetailDrawer';
@@ -59,7 +59,7 @@ export default function DrawersContainer() {
<CashflowTransactionDetailDrawer
name={DRAWERS.CASHFLOW_TRNASACTION_DETAILS}
/>
<QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} />
{/* <QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} /> */}
<QuickCreateItemDrawer name={DRAWERS.QUICK_CREATE_ITEM} />
<QuickWriteVendorDrawer name={DRAWERS.QUICK_WRITE_VENDOR} />
<CreditNoteDetailDrawer name={DRAWERS.CREDIT_NOTE_DETAILS} />
@@ -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,18 +16,14 @@ export interface DisplayNameListProps
'items' | 'valueAccessor' | 'textAccessor' | 'labelAccessor'
> {}
export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
const {
values: {
first_name: firstName,
last_name: lastName,
company_name: companyName,
salutation,
},
} = useFormikContext<any>();
const formats = useMemo(
() => [
function useDisplayNameFormatOptions(
salutation?: string,
firstName?: string,
lastName?: string,
companyName?: string,
): DisplayNameListItem[] {
return useMemo(() => {
const formats: DisplayNameFormat[] = [
{
format: '{1} {2} {3}',
values: [salutation, firstName, lastName],
@@ -31,13 +32,9 @@ export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
{ 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
return formats
.filter(
(format) =>
!format.values.some((value, index) => {
@@ -52,9 +49,29 @@ export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
const replaceWith = value || '';
label = label.replace(`{${index + 1}}`, replaceWith).trim();
});
return { label: label.replace(/\s+/g, ' ') };
}),
[formats],
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: {
first_name: firstName,
last_name: lastName,
company_name: companyName,
salutation,
},
} = useFormikContext<any>();
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}
@@ -7,11 +7,11 @@ import {
FInputGroup,
FTextArea,
} from '@/components';
import CustomerFormSectionTitle from './CustomerFormSectionTitle';
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
export default function CustomerBillingAddress() {
export function CustomerBillingAddress() {
return (
<Box>
<Box data-section-id="billingAddress">
<CustomerFormSectionTitle>
<T id={'billing_address'} />
</CustomerFormSectionTitle>
@@ -1,4 +1,3 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import {
@@ -11,40 +10,32 @@ 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 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();
// 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) => {
const handleClearBtnClick = (_event: React.MouseEvent<HTMLElement>) => {
resetForm();
};
// Handle submit & new button click.
const handleSubmitAndNewClick = (event) => {
const handleSubmitAndNewClick = (_event: React.MouseEvent<HTMLElement>) => {
submitForm();
setSubmitPayload({ noRedirect: true });
};
@@ -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>
);
}
@@ -14,18 +14,15 @@ import {
Icon,
Stack,
} from '@/components';
import CustomerTypeRadioField from './CustomerTypeRadioField';
import CustomerFormSectionTitle from './CustomerFormSectionTitle';
import { CustomerTypeRadioField } from './CustomerTypeRadioField';
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
import { useAutofocus } from '@/hooks';
/**
* Customer form primary section.
*/
export default function CustomerFormPrimarySection({}) {
export function CustomerFormBasicSection({}) {
const firstNameFieldRef = useAutofocus();
return (
<Box>
<Box data-section-id="primary">
<CustomerFormSectionTitle>Customer details</CustomerFormSectionTitle>
{/**-----------Customer type. -----------*/}
@@ -102,7 +99,7 @@ export default function CustomerFormPrimarySection({}) {
<FFormGroup
name={'email'}
label={<T id={'vendor_email'} />}
inline={true}
inline
>
<FInputGroup
name={'email'}
@@ -118,7 +115,11 @@ export default function CustomerFormPrimarySection({}) {
inline={true}
>
<Stack spacing={10}>
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} />
<FInputGroup
name={'work_phone'}
placeholder={intl.get('work')}
leftIcon="phone"
/>
<FInputGroup
name={'personal_phone'}
placeholder={intl.get('mobile')}
@@ -0,0 +1,53 @@
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";
const customerFormSections = {
primary: 'primary',
financial: 'financial',
billingAddress: 'billingAddress',
shippingAddress: 'shippingAddress',
notes: 'notes',
};
export function CustomerFormContent() {
const [selectedTabId, setSelectedTabId] = useState(customerFormSections.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
vertical
large
selectedTabId={selectedTabId}
onChange={handleTabChange}
className={css`position: sticky; top: 20px; .bp4-large > .bp4-tab{font-size: 14px;} `}
>
<Tab id={customerFormSections.primary} title={'Basic'} />
<Tab id={customerFormSections.financial} title={'Financial'} />
<Tab id={customerFormSections.billingAddress} title={'Billing address'} />
<Tab id={customerFormSections.shippingAddress} title={'Ship address'} />
<Tab id={customerFormSections.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>
);
}
@@ -1,4 +1,3 @@
// @ts-nocheck
import React from 'react';
import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
import { ErrorMessage, useFormikContext } from 'formik';
@@ -10,8 +9,6 @@ import {
CurrencySelectList,
BranchSelect,
FeatureCan,
Row,
Col,
FMoneyInputGroup,
ExchangeRateInputGroup,
FDateInput,
@@ -24,23 +21,20 @@ import {
useSetPrimaryBranchToForm,
} from './utils';
import { useCurrentOrganization } from '@/hooks/state';
import CustomerFormSectionTitle from './CustomerFormSectionTitle';
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 (
<Box>
<Box data-section-id="financial">
<CustomerFormSectionTitle>
<T id={'financial'} />
</CustomerFormSectionTitle>
{/*------------ Currency -----------*/}
<FFormGroup
name={'currency_code'}
label={<T id={'currency'} />}
@@ -55,16 +49,10 @@ export default function CustomerFinancialPanel() {
/>
</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'} />}
@@ -94,7 +82,7 @@ function CustomerOpeningBalanceAtField() {
if (customerId) return null;
return (
<FormGroup
<FFormGroup
name={'opening_balance_at'}
label={<T id={'opening_balance_at'} />}
inline
@@ -109,14 +97,10 @@ function CustomerOpeningBalanceAtField() {
parseDate={(str) => new Date(str)}
fill={true}
/>
</FormGroup>
</FFormGroup>
);
}
/**
* Customer opening balance field.
* @returns {JSX.Element}
*/
function CustomerOpeningBalanceField() {
const { customerId } = useCustomerFormContext();
const { values } = useFormikContext();
@@ -129,15 +113,16 @@ function CustomerOpeningBalanceField() {
label={<T id={'opening_balance'} />}
name={'opening_balance'}
inline
fill
shouldUpdate={openingBalanceFieldShouldUpdate}
shouldUpdateDeps={{ currencyCode: values.currency_code }}
fastField={true}
fill
>
<ControlGroup fill>
<InputPrependText text={values.currency_code} />
<InputPrependText text={values.currency_code as string} />
<FMoneyInputGroup
name={'opening_balance'}
fastField
inputGroupProps={{ fill: true }}
/>
</ControlGroup>
@@ -145,11 +130,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();
@@ -162,17 +142,14 @@ function CustomerOpeningBalanceExchangeRateField() {
return null;
}
return (
<FFormGroup
label={' '}
name={'opening_balance_exchange_rate'}
inline
fill
>
<ExchangeRateInputGroup
fromCurrency={values.currency_code}
toCurrency={currentOrganization.base_currency}
name={'opening_balance_exchange_rate'}
onRecalcConfirm={() => {}}
onCancel={() => {}}
formGroupProps={{ label: ' ' }}
/>
</FFormGroup>
);
}
@@ -1,39 +1,97 @@
// @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 { Divider, Intent, Tab, Tabs } from '@blueprintjs/core';
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 { css } from '@emotion/css';
import { AppToaster, Box, Card, Group } from '@/components';
import CustomerFormPrimarySection from './CustomerFormPrimarySection';
import CustomerFormAfterPrimarySection from './CustomerFormAfterPrimarySection';
import CustomersTabs from './CustomersTabs';
import { CustomerFloatingActions } from './CustomerFloatingActions';
import { AppToaster, Box } from '@/components';
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
import CustomerFinancialPanel from './CustomerFinancialPanel';
import CustomerShippingAddress from './CustomerShippingAddress';
import CustomerBillingAddress from './CustomerBillingAddress';
import { CustomerFormContent } from './CustomerFormContent';
function CustomerFormFormik({
type CustomerFormValues = {
customer_type: string;
salutation: string;
first_name: string;
last_name: string;
company_name: string;
display_name: string;
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,
@@ -43,28 +101,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
@@ -86,30 +144,25 @@ function CustomerFormFormik({
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, className)}
>
<Formik
<Box mx={'auto'} maxWidth={800}>
<Formik<CustomerFormValues>
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<Form>
<CustomerFormFields>
<Box px={'20px'} py={'10px'} mx={'auto'} maxWidth={'800px'}>
<CustomerFormContent />
</Box>
</CustomerFormFields>
</Form>
</Formik>
</div>
</Box>
);
}
@@ -124,56 +177,4 @@ const CustomerFormFields = styled.div`
}
`;
export const CustomerFormHeaderPrimary = styled.div`
--x-border: #e4e4e4;
.bp4-dark & {
--x-border: var(--color-dark-gray3);
}
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);
function CustomerFormContent() {
return (
<Card>
<Group verticalAlign={'top'} alignItems={'flex-start'} flexWrap={'nowrap'}>
<Tabs vertical large defaultSelectedTabId={'primary'} className={css`position: sticky; top: 20px;`}>
<Tab id={'primary'} title={'Basic'} />
<Tab id={'financial'} title={'Financial'} />
<Tab id={'billing_address'} title={'Billing address'} />
<Tab id={'shipping_address'} title={'Ship address'} />
</Tabs>
<CustomerFormBasicSection />
</Group>
<CustomerFloatingActions />
</Card>
)
}
const customerFormSectionDividerClass = css`
margin: 20px 0;
`;
function CustomerFormBasicSection() {
return (
<Box>
<CustomerFormPrimarySection />
<Divider className={customerFormSectionDividerClass} />
<CustomerFinancialPanel />
<Divider className={customerFormSectionDividerClass} />
<CustomerBillingAddress />
<Divider className={customerFormSectionDividerClass} />
<CustomerShippingAddress />
</Box>
);
}
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>
);
}
@@ -5,7 +5,7 @@ import styled from 'styled-components';
import { DashboardCard, DashboardInsider } from '@/components';
import CustomerFormFormik from './CustomerFormFormik';
import { CustomerFormFormik } from './CustomerFormFormik';
import {
CustomerFormProvider,
useCustomerFormContext,
@@ -19,9 +19,9 @@ function CustomerFormPageLoading({ children }) {
const { isFormLoading } = useCustomerFormContext();
return (
<CustomerDashboardInsider loading={isFormLoading}>
<DashboardInsider loading={isFormLoading}>
{children}
</CustomerDashboardInsider>
</DashboardInsider>
);
}
@@ -49,7 +49,7 @@ export default function CustomerFormPage() {
return (
<CustomerFormProvider customerId={customerId}>
<CustomerFormPageLoading>
<CustomerFormPageFormik
<CustomerFormFormik
onSubmitSuccess={handleSubmitSuccess}
onCancel={handleFormCancel}
/>
@@ -57,9 +57,3 @@ export default function CustomerFormPage() {
</CustomerFormProvider>
);
}
const CustomerFormPageFormik = styled(CustomerFormFormik)`
`;
const CustomerDashboardInsider = styled(DashboardInsider)`
`;
@@ -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,
);
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,21 @@ 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);
const useCustomerFormContext = () => {
const ctx = React.useContext(CustomerFormContext);
if (!ctx) {
throw new Error(
'useCustomerFormContext must be used within a CustomerFormProvider',
);
}
return ctx;
};
export { CustomerFormProvider, useCustomerFormContext };
@@ -1,4 +1,3 @@
// @ts-nocheck
import React from 'react';
import { css } from '@emotion/css';
@@ -9,6 +8,6 @@ const customerFormSectionTitleClass = css`
margin-top: 0;
`;
export default function CustomerFormSectionTitle({ children }) {
export function CustomerFormSectionTitle({ children }: { children: React.ReactNode | string }) {
return <h4 className={customerFormSectionTitleClass}>{children}</h4>;
}
@@ -7,11 +7,11 @@ import {
FInputGroup,
FTextArea,
} from '@/components';
import CustomerFormSectionTitle from './CustomerFormSectionTitle';
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
export default function CustomerShippingAddress() {
export function CustomerShippingAddress() {
return (
<Box>
<Box data-section-id="shippingAddress">
<CustomerFormSectionTitle>
<T id={'shipping_address'} />
</CustomerFormSectionTitle>
@@ -8,7 +8,7 @@ import { FormattedMessage as T, FFormGroup } from '@/components';
/**
* Customer type selector (button group).
*/
export default function CustomerTypeRadioField() {
export function CustomerTypeRadioField() {
return (
<FFormGroup
name={'customer_type'}
@@ -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() {
@@ -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';
@@ -56,11 +54,11 @@ function QuickCustomerFormDrawer({
<CustomerFormProvider customerId={customerId}>
<DrawerCustomerFormLoading>
<CustomerFormCard>
<CustomerFormFormik
{/* <CustomerFormFormik
initialValues={{ first_name: displayName }}
onSubmitSuccess={handleSubmitSuccess}
onCancel={handleCancelForm}
/>
/> */}
</CustomerFormCard>
</DrawerCustomerFormLoading>
</CustomerFormProvider>
@@ -74,9 +72,9 @@ const CustomerFormCard = styled(Card)`
padding: 25px;
margin-bottom: calc(15px + 65px);
${CustomerFormHeaderPrimary} {
padding-top: 0;
}
// ${CustomerFormHeaderPrimary} {
// padding-top: 0;
// }
.page-form {
padding: 0;