re-structure to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 01:02:31 +02:00
parent 8242ec64ba
commit 7a0a13f9d5
10400 changed files with 46966 additions and 17223 deletions

View File

@@ -0,0 +1,231 @@
// @ts-nocheck
import React from 'react';
import { FormGroup, InputGroup, TextArea } from '@blueprintjs/core';
import { Row, Col } from '@/components';
import { FormattedMessage as T } from '@/components';
import { FastField, ErrorMessage } from 'formik';
import { inputIntent } from '@/utils';
const CustomerBillingAddress = ({}) => {
return (
<div className={'tab-panel--address'}>
<Row>
<Col xs={6}>
<h4>
<T id={'billing_address'} />
</h4>
{/*------------ Billing Address country -----------*/}
<FastField name={'billing_address_country'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="billing_address_country" />}
label={<T id={'country'} />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Billing Address 1 -----------*/}
<FastField name={'billing_address_1'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'address_line_1'} />}
className={'form-group--address_line_1'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="billing_address_1" />}
>
<TextArea {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Billing Address 2 -----------*/}
<FastField name={'billing_address_2'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'address_line_2'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="billing_address_2" />}
>
<TextArea {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Billing Address city -----------*/}
<FastField name={'billing_address_city'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'city_town'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="billing_address_city" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Billing Address state -----------*/}
<FastField name={'billing_address_state'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'state'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="billing_address_state" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Billing Address postcode -----------*/}
<FastField name={'billing_address_postcode'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'zip_code'} />}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="billing_address_postcode" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Billing Address phone -----------*/}
<FastField name={'billing_address_phone'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'phone'} />}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="billing_address_phone" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
</Col>
<Col xs={6}>
<h4>
<T id={'shipping_address'} />
</h4>
{/*------------ Shipping Address country -----------*/}
<FastField name={'shipping_address_country'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'country'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="shipping_address_country" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Shipping Address 1 -----------*/}
<FastField name={'shipping_address_1'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'address_line_1'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="shipping_address_1" />}
>
<TextArea {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Shipping Address 2 -----------*/}
<FastField name={'shipping_address_2'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'address_line_2'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="shipping_address_2" />}
>
<TextArea {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Shipping Address city -----------*/}
<FastField name={'shipping_address_city'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'city_town'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="shipping_address_city" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Shipping Address state -----------*/}
<FastField name={'shipping_address_state'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'state'} />}
className={'form-group--journal-number'}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="shipping_address_state" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Shipping Address postcode -----------*/}
<FastField name={'shipping_address_postcode'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'zip_code'} />}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="shipping_address_postcode" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Shipping Address phone -----------*/}
<FastField name={'shipping_address_phone'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'phone'} />}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="shipping_address_phone" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
</Col>
</Row>
</div>
);
};
export default CustomerBillingAddress;

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
import React, {
useMemo,
useState,
useEffect,
useRef,
useCallback,
} from 'react';
import { Dragzone, FormattedMessage as T } from '@/components';
function CustomerAttachmentTabs() {
return (
<div>
<Dragzone
initialFiles={[]}
onDrop={null}
onDeleteFile={[]}
hint={<T id={'attachments_maximum'} />}
/>
</div>
);
}
export default CustomerAttachmentTabs;

View File

@@ -0,0 +1,134 @@
// @ts-nocheck
import React from 'react';
import moment from 'moment';
import classNames from 'classnames';
import { FormGroup, Position, Classes, ControlGroup } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage } from 'formik';
import { Features } from '@/constants';
import {
FFormGroup,
FormattedMessage as T,
MoneyInputGroup,
InputPrependText,
CurrencySelectList,
BranchSelect,
BranchSelectButton,
FeatureCan,
Row,
Col,
} from '@/components';
import { useCustomerFormContext } from './CustomerFormProvider';
import { useSetPrimaryBranchToForm } from './utils';
import { momentFormatter, tansformDateValue, inputIntent } from '@/utils';
/**
* Customer financial panel.
*/
export default function CustomerFinancialPanel() {
const { currencies, customerId, branches } = useCustomerFormContext();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={'tab-panel--financial'}>
<Row>
<Col xs={6}>
{/*------------ Opening balance at -----------*/}
<FastField name={'opening_balance_at'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'opening_balance_at'} />}
className={classNames('form-group--select-list', Classes.FILL)}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="opening_balance_at" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={(date) => {
form.setFieldValue(
'opening_balance_at',
moment(date).format('YYYY-MM-DD'),
);
}}
value={tansformDateValue(value)}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
disabled={customerId}
/>
</FormGroup>
)}
</FastField>
{/*------------ Opening balance -----------*/}
<FastField name={'opening_balance'}>
{({ form, field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'opening_balance'} />}
className={classNames(
'form-group--opening-balance',
Classes.FILL,
)}
intent={inputIntent({ error, touched })}
inline={true}
>
<ControlGroup>
<InputPrependText text={form.values.currency_code} />
<MoneyInputGroup
value={value}
inputGroupProps={{ fill: true }}
disabled={customerId}
onChange={(balance) => {
form.setFieldValue('opening_balance', balance);
}}
/>
</ControlGroup>
</FormGroup>
)}
</FastField>
{/*------------ Opening branch -----------*/}
<FeatureCan feature={Features.Branches}>
<FFormGroup
label={<T id={'customer.label.opening_branch'} />}
name={'opening_balance_branch_id'}
inline={true}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'opening_balance_branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FFormGroup>
</FeatureCan>
{/*------------ Currency -----------*/}
<FastField name={'currency_code'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency'} />}
className={classNames(
'form-group--select-list',
'form-group--balance-currency',
Classes.FILL,
)}
inline={true}
>
<CurrencySelectList
currenciesList={currencies}
selectedCurrencyCode={value}
onCurrencySelected={(currency) => {
form.setFieldValue('currency_code', currency.currency_code);
}}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
</div>
);
}

View File

@@ -0,0 +1,105 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import {
Intent,
Button,
ButtonGroup,
Popover,
PopoverInteractionKind,
Position,
Menu,
MenuItem,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { 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 }) {
// Customer form context.
const { isNewMode, setSubmitPayload } = useCustomerFormContext();
// Formik context.
const { resetForm, submitForm, isSubmitting } = useFormikContext();
// Handle submit button click.
const handleSubmitBtnClick = (event) => {
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) => {
submitForm();
setSubmitPayload({ noRedirect: true });
};
return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
<ButtonGroup>
{/* ----------- Save and New ----------- */}
<SaveButton
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitBtnClick}
text={!isNewMode ? <T id={'edit'} /> : <T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitAndNewClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</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'} />}
/>
</div>
);
}
const SaveButton = styled(Button)`
min-width: 100px;
`;

View File

@@ -0,0 +1,50 @@
// @ts-nocheck
import * as Yup from 'yup';
import intl from 'react-intl-universal';
const Schema = Yup.object().shape({
customer_type: Yup.string()
.required()
.trim()
.label(intl.get('customer_type_')),
salutation: Yup.string().trim(),
first_name: Yup.string().trim(),
last_name: Yup.string().trim(),
company_name: Yup.string().trim(),
display_name: Yup.string()
.trim()
.required()
.label(intl.get('display_name_')),
email: Yup.string().email().nullable(),
work_phone: Yup.number(),
personal_phone: Yup.number(),
website: Yup.string().url().nullable(),
active: Yup.boolean(),
note: Yup.string().trim(),
billing_address_country: Yup.string().trim(),
billing_address_1: Yup.string().trim(),
billing_address_2: Yup.string().trim(),
billing_address_city: Yup.string().trim(),
billing_address_state: Yup.string().trim(),
billing_address_postcode: Yup.number().nullable(),
billing_address_phone: Yup.number(),
shipping_address_country: Yup.string().trim(),
shipping_address_1: Yup.string().trim(),
shipping_address_2: Yup.string().trim(),
shipping_address_city: Yup.string().trim(),
shipping_address_state: Yup.string().trim(),
shipping_address_postcode: Yup.number().nullable(),
shipping_address_phone: Yup.number(),
opening_balance: Yup.number().nullable(),
currency_code: Yup.string(),
opening_balance_at: Yup.date(),
opening_balance_branch_id: Yup.string(),
});
export const CreateCustomerForm = Schema;
export const EditCustomerForm = Schema;

View File

@@ -0,0 +1,15 @@
// @ts-nocheck
import React from 'react';
import { CustomerFormProvider } from './CustomerFormProvider';
import CustomerFormFormik from './CustomerFormFormik';
/**
* Abstructed customer form.
*/
export default function CustomerForm({ customerId }) {
return (
<CustomerFormProvider customerId={customerId}>
<CustomerFormFormik />
</CustomerFormProvider>
);
}

View File

@@ -0,0 +1,72 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core';
import { FastField, ErrorMessage } from 'formik';
import { FormattedMessage as T } from '@/components';
import { inputIntent } from '@/utils';
export default function CustomerFormAfterPrimarySection({}) {
return (
<div class="customer-form__after-primary-section-content">
{/*------------ Customer email -----------*/}
<FastField name={'email'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'email'} />}
className={'form-group--email'}
label={<T id={'customer_email'} />}
inline={true}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Phone number -----------*/}
<FormGroup
className={'form-group--phone-number'}
label={<T id={'phone_number'} />}
inline={true}
>
<ControlGroup>
<FastField name={'personal_phone'}>
{({ field, meta: { error, touched } }) => (
<InputGroup
intent={inputIntent({ error, touched })}
placeholder={intl.get('personal')}
{...field}
/>
)}
</FastField>
<FastField name={'work_phone'}>
{({ field, meta: { error, touched } }) => (
<InputGroup
intent={inputIntent({ error, touched })}
placeholder={intl.get('work')}
{...field}
/>
)}
</FastField>
</ControlGroup>
</FormGroup>
{/*------------ Customer website -----------*/}
<FastField name={'website'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'website'} />}
className={'form-group--website'}
label={<T id={'website'} />}
inline={true}
>
<InputGroup placeholder={'http://'} {...field} />
</FormGroup>
)}
</FastField>
</div>
);
}

View File

@@ -0,0 +1,126 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import { CLASSES } from '@/constants/classes';
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
import { compose, transformToForm, saveInvoke } 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 '@/style/pages/Customers/Form.scss';
/**
* Customer form.
*/
function CustomerFormFormik({
organization: { base_currency },
// #ownProps
initialValues: initialCustomerValues,
onSubmitSuccess,
onSubmitError,
onCancel,
className,
}) {
const {
customer,
submitPayload,
contactDuplicate,
editCustomerMutate,
createCustomerMutate,
isNewMode,
} = useCustomerFormContext();
/**
* Initial values in create and edit mode.
*/
const initialValues = useMemo(
() => ({
...defaultInitialValues,
currency_code: base_currency,
...transformToForm(contactDuplicate || customer, defaultInitialValues),
...initialCustomerValues,
}),
[customer, contactDuplicate, base_currency, initialCustomerValues],
);
// Handles the form submit.
const handleFormSubmit = (values, formArgs) => {
const { setSubmitting, resetForm } = formArgs;
const formValues = { ...values };
const onSuccess = () => {
AppToaster.show({
message: intl.get(
isNewMode
? 'the_customer_has_been_created_successfully'
: 'the_item_customer_has_been_edited_successfully',
),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
saveInvoke(onSubmitSuccess, values, formArgs, submitPayload);
};
const onError = () => {
setSubmitting(false);
saveInvoke(onSubmitError, values, formArgs, submitPayload);
};
if (isNewMode) {
createCustomerMutate(formValues).then(onSuccess).catch(onError);
} else {
editCustomerMutate([customer.id, formValues])
.then(onSuccess)
.catch(onError);
}
};
return (
<div
className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_CUSTOMER,
className,
)}
>
<Formik
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<Form>
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
<CustomerFormPrimarySection />
</div>
<div className={'page-form__after-priamry-section'}>
<CustomerFormAfterPrimarySection />
</div>
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
<CustomersTabs />
</div>
<CustomerFloatingActions onCancel={onCancel} />
</Form>
</Formik>
</div>
);
}
export default compose(withCurrentOrganization())(CustomerFormFormik);

View File

@@ -0,0 +1,74 @@
// @ts-nocheck
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 {
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>
</CustomerFormProvider>
);
}
const CustomerFormPageFormik = styled(CustomerFormFormik)`
.page-form {
&__floating-actions {
margin-left: -40px;
margin-right: -40px;
}
}
`;
const CustomerDashboardInsider = styled(DashboardInsider)`
padding-bottom: 64px;
`;

View File

@@ -0,0 +1,127 @@
// @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,
} 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 -----------*/}
<FormGroup
className={classNames('form-group--contact_name')}
label={<T id={'contact_name'} />}
inline={true}
>
<ControlGroup>
<FastField name={'salutation'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<SalutationList
onItemSelect={(salutation) => {
form.setFieldValue('salutation', salutation.label);
}}
selectedItem={value}
popoverProps={{ minimal: true }}
className={classNames(
CLASSES.FORM_GROUP_LIST_SELECT,
CLASSES.FILL,
'input-group--salutation-list',
'select-list--fill-button',
)}
/>
)}
</FastField>
<FastField name={'first_name'}>
{({ field, meta: { error, touched } }) => (
<InputGroup
placeholder={intl.get('first_name')}
intent={inputIntent({ error, touched })}
className={classNames('input-group--first-name')}
inputRef={(ref) => (firstNameFieldRef.current = ref)}
{...field}
/>
)}
</FastField>
<FastField name={'last_name'}>
{({ field, meta: { error, touched } }) => (
<InputGroup
placeholder={intl.get('last_name')}
intent={inputIntent({ error, touched })}
className={classNames('input-group--last-name')}
{...field}
/>
)}
</FastField>
</ControlGroup>
</FormGroup>
{/*----------- Company Name -----------*/}
<FastField name={'company_name'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
className={classNames('form-group--company_name')}
label={<T id={'company_name'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'company_name'} />}
inline={true}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*----------- Display Name -----------*/}
<Field name={'display_name'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
helperText={<ErrorMessage name={'display_name'} />}
intent={inputIntent({ error, touched })}
label={
<>
<T id={'display_name'} />
<FieldRequiredHint />
<Hint />
</>
}
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, CLASSES.FILL)}
inline={true}
>
<DisplayNameList
firstName={form.values.first_name}
lastName={form.values.last_name}
company={form.values.company_name}
salutation={form.values.salutation}
onItemSelect={(displayName) => {
form.setFieldValue('display_name', displayName.label);
}}
selectedItem={value}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</Field>
</div>
);
}

View File

@@ -0,0 +1,81 @@
// @ts-nocheck
import React, { useState, createContext } from 'react';
import { useLocation } from 'react-router-dom';
import {
useCustomer,
useCurrencies,
useCreateCustomer,
useEditCustomer,
useContact,
useBranches,
} from '@/hooks/query';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
const CustomerFormContext = createContext();
function CustomerFormProvider({ query, customerId, ...props }) {
const { state } = useLocation();
const contactId = state?.action;
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Handle fetch customer details.
const { data: customer, isLoading: isCustomerLoading } = useCustomer(
customerId,
{ enabled: !!customerId },
);
// Handle fetch contact duplicate details.
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
contactId,
{ enabled: !!contactId },
);
// Handle fetch Currencies data table
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// Form submit payload.
const [submitPayload, setSubmitPayload] = useState({});
const { mutateAsync: editCustomerMutate } = useEditCustomer();
const { mutateAsync: createCustomerMutate } = useCreateCustomer();
// determines whether the form new or duplicate mode.
const isNewMode = contactId || !customerId;
const isFormLoading =
isCustomerLoading || isCurrenciesLoading || isBranchesLoading;
const provider = {
customerId,
customer,
currencies,
branches,
contactDuplicate,
submitPayload,
isNewMode,
isCustomerLoading,
isCurrenciesLoading,
isBranchesSuccess,
isFormLoading,
setSubmitPayload,
editCustomerMutate,
createCustomerMutate,
};
return <CustomerFormContext.Provider value={provider} {...props} />;
}
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
export { CustomerFormProvider, useCustomerFormContext };

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { FormGroup, TextArea, Classes } from '@blueprintjs/core';
import { FastField, ErrorMessage } from 'formik';
import { FormattedMessage as T } from '@/components';
import { inputIntent } from '@/utils';
export default function CustomerNotePanel({ errors, touched, getFieldProps }) {
return (
<div className={'tab-panel--note'}>
<FastField name={'note'}>
{({ field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'note'} />}
className={classNames('form-group--note', Classes.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="payment_date" />}
>
<TextArea {...field} />
</FormGroup>
)}
</FastField>
</div>
);
}

View File

@@ -0,0 +1,44 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { RadioGroup, Radio, FormGroup } from '@blueprintjs/core';
import { FormattedMessage as T } from '@/components';
import { FastField } from 'formik';
import { handleStringChange, saveInvoke } from '@/utils';
/**
* Customer type radio field.
*/
export default function RadioCustomer(props) {
const { onChange, ...rest } = props;
return (
<FastField name={'customer_type'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
inline={true}
label={<T id={'customer_type'} />}
className={classNames('form-group--customer_type')}
>
<RadioGroup
inline={true}
onChange={handleStringChange((value) => {
saveInvoke(onChange, value);
form.setFieldValue('customer_type', value);
})}
selectedValue={value}
>
<Radio label={intl.get('business')} value="business" />
<Radio
label={intl.get('individual')}
value="individual"
/>
</RadioGroup>
</FormGroup>
)}
</FastField>
);
}

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Tabs, Tab } from '@blueprintjs/core';
import CustomerAddressTabs from './CustomerAddressTabs';
import CustomerAttachmentTabs from './CustomerAttachmentTabs';
import CustomerFinancialPanel from './CustomerFinancialPanel';
import CustomerNotePanel from './CustomerNotePanel';
export default function CustomersTabs() {
return (
<div>
<Tabs
animate={true}
id={'customer-tabs'}
large={true}
defaultSelectedTabId="financial"
>
<Tab
id={'financial'}
title={intl.get('financial_details')}
panel={<CustomerFinancialPanel />}
/>
<Tab
id={'address'}
title={intl.get('address')}
panel={<CustomerAddressTabs />}
/>
<Tab
id="notes"
title={intl.get('notes')}
panel={<CustomerNotePanel />}
/>
<Tab
id={'attachement'}
title={intl.get('attachement')}
panel={<CustomerAttachmentTabs />}
/>
</Tabs>
</div>
);
}

View File

@@ -0,0 +1,59 @@
// @ts-nocheck
import React from 'react';
import moment from 'moment';
import { useFormikContext } from 'formik';
import { first } from 'lodash';
import { useCustomerFormContext } from './CustomerFormProvider';
export const defaultInitialValues = {
customer_type: 'business',
salutation: '',
first_name: '',
last_name: '',
company_name: '',
display_name: '',
email: '',
work_phone: '',
personal_phone: '',
website: '',
note: '',
active: true,
billing_address_country: '',
billing_address_1: '',
billing_address_2: '',
billing_address_city: '',
billing_address_state: '',
billing_address_postcode: '',
billing_address_phone: '',
shipping_address_country: '',
shipping_address_1: '',
shipping_address_2: '',
shipping_address_city: '',
shipping_address_state: '',
shipping_address_postcode: '',
shipping_address_phone: '',
opening_balance: '',
currency_code: '',
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
opening_balance_branch_id: '',
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useCustomerFormContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('opening_balance_branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -0,0 +1,21 @@
// @ts-nocheck
import React from 'react';
const CustomerDeleteAlert = React.lazy(
() => import('@/containers/Alerts/Customers/CustomerDeleteAlert'),
);
const CustomerActivateAlert = React.lazy(
() => import('@/containers/Alerts/Customers/CustomerActivateAlert'),
);
const CustomerInactivateAlert = React.lazy(
() => import('@/containers/Alerts/Customers/CustomerInactivateAlert'),
);
/**
* Customers alert.
*/
export default [
{ name: 'customer-delete', component: CustomerDeleteAlert },
{ name: 'customer-activate', component: CustomerActivateAlert },
{ name: 'customer-inactivate', component: CustomerInactivateAlert },
];

View File

@@ -0,0 +1,189 @@
// @ts-nocheck
import React from 'react';
import {
NavbarGroup,
NavbarDivider,
Button,
Classes,
Intent,
Switch,
Alignment,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
If,
Icon,
Can,
FormattedMessage as T,
DashboardActionViewsList,
AdvancedFilterPopover,
DashboardFilterButton,
DashboardRowsHeightButton,
DashboardActionsBar,
} from '@/components';
import { useCustomersListContext } from './CustomersListProvider';
import { useRefreshCustomers } from '@/hooks/query/customers';
import withCustomers from './withCustomers';
import withCustomersActions from './withCustomersActions';
import withAlertActions from '@/containers/Alert/withAlertActions';
import withSettingsActions from '@/containers/Settings/withSettingsActions';
import withSettings from '@/containers/Settings/withSettings';
import { CustomerAction, AbilitySubject } from '@/constants/abilityOption';
import { compose } from '@/utils';
/**
* Customers actions bar.
*/
function CustomerActionsBar({
// #withCustomers
customersSelectedRows = [],
customersFilterConditions,
// #withCustomersActions
setCustomersTableState,
accountsInactiveMode,
// #withAlertActions
openAlert,
// #withSettings
customersTableSize,
// #withSettingsActions
addSetting,
}) {
// History context.
const history = useHistory();
// Customers list context.
const { customersViews, fields } = useCustomersListContext();
// Customers refresh action.
const { refresh } = useRefreshCustomers();
const onClickNewCustomer = () => {
history.push('/customers/new');
};
// Handle Customers bulk delete button click.,
const handleBulkDelete = () => {
openAlert('customers-bulk-delete', { customersIds: customersSelectedRows });
};
const handleTabChange = (view) => {
setCustomersTableState({
viewSlug: view ? view.slug : null,
});
};
// Handle inactive switch changing.
const handleInactiveSwitchChange = (event) => {
const checked = event.target.checked;
setCustomersTableState({ inactiveMode: checked });
};
// Handle click a refresh customers
const handleRefreshBtnClick = () => {
refresh();
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('customers', 'tableSize', size);
};
return (
<DashboardActionsBar>
<NavbarGroup>
<DashboardActionViewsList
resourceName={'customers'}
views={customersViews}
allMenuItem={true}
allMenuItemText={<T id={'all'} />}
onChange={handleTabChange}
/>
<NavbarDivider />
<Can I={CustomerAction.Create} a={AbilitySubject.Item}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'plus'} />}
text={<T id={'new_customer'} />}
onClick={onClickNewCustomer}
/>
<NavbarDivider />
</Can>
<AdvancedFilterPopover
advancedFilterProps={{
conditions: customersFilterConditions,
defaultFieldKey: 'display_name',
fields: fields,
onFilterChange: (filterConditions) => {
setCustomersTableState({ filterRoles: filterConditions });
},
}}
>
<DashboardFilterButton
conditionsCount={customersFilterConditions.length}
/>
</AdvancedFilterPopover>
<If condition={customersSelectedRows.length}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleBulkDelete}
/>
</If>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-import-16" iconSize={16} />}
text={<T id={'import'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={customersTableSize}
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
<Can I={CustomerAction.Edit} a={AbilitySubject.Customer}>
<Switch
labelElement={<T id={'inactive'} />}
defaultChecked={accountsInactiveMode}
onChange={handleInactiveSwitchChange}
/>
</Can>
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="refresh-16" iconSize={14} />}
onClick={handleRefreshBtnClick}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withCustomersActions,
withSettingsActions,
withCustomers(({ customersSelectedRows, customersTableState }) => ({
customersSelectedRows,
accountsInactiveMode: customersTableState.inactiveMode,
customersFilterConditions: customersTableState.filterRoles,
})),
withSettings(({ customersSettings }) => ({
customersTableSize: customersSettings?.tableSize,
})),
withAlertActions,
)(CustomerActionsBar);

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
import React from 'react';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { Can, FormattedMessage as T, EmptyStatus } from '@/components';
import { AbilitySubject, CustomerAction } from '@/constants/abilityOption';
export default function CustomersEmptyStatus() {
const history = useHistory();
return (
<EmptyStatus
title={<T id={'create_and_manage_your_organization_s_customers'} />}
description={
<p>
<T id={'here_a_list_of_your_organization_products_and_services'} />
</p>
}
action={
<>
<Can I={CustomerAction.Create} a={AbilitySubject.Customer}>
<Button
intent={Intent.PRIMARY}
large={true}
onClick={() => {
history.push('/customers/new');
}}
>
<T id={'new_customer'} />
</Button>
<Button intent={Intent.NONE} large={true}>
<T id={'learn_more'} />
</Button>
</Can>
</>
}
/>
);
}

View File

@@ -0,0 +1,58 @@
// @ts-nocheck
import React, { useEffect } from 'react';
import '@/style/pages/Customers/List.scss';
import { DashboardPageContent } from '@/components';
import CustomersActionsBar from './CustomersActionsBar';
import CustomersViewsTabs from './CustomersViewsTabs';
import CustomersTable from './CustomersTable';
import { CustomersListProvider } from './CustomersListProvider';
import withCustomers from './withCustomers';
import withCustomersActions from './withCustomersActions';
import { compose } from '@/utils';
/**
* Customers list.
*/
function CustomersList({
// #withCustomers
customersTableState,
customersTableStateChanged,
// #withCustomersActions
resetCustomersTableState,
}) {
// Resets the accounts table state once the page unmount.
useEffect(
() => () => {
resetCustomersTableState();
},
[resetCustomersTableState],
);
return (
<CustomersListProvider
tableState={customersTableState}
tableStateChanged={customersTableStateChanged}
>
<CustomersActionsBar />
<DashboardPageContent>
<CustomersViewsTabs />
<CustomersTable />
</DashboardPageContent>
</CustomersListProvider>
);
}
export default compose(
withCustomers(({ customersTableState, customersTableStateChanged }) => ({
customersTableState,
customersTableStateChanged,
})),
withCustomersActions,
)(CustomersList);

View File

@@ -0,0 +1,67 @@
// @ts-nocheck
import React, { createContext } from 'react';
import { isEmpty } from 'lodash';
import { DashboardInsider } from '@/components';
import { useResourceMeta, useResourceViews, useCustomers } from '@/hooks/query';
import { getFieldsFromResourceMeta } from '@/utils';
import { transformCustomersStateToQuery } from './utils';
const CustomersListContext = createContext();
function CustomersListProvider({ tableState, tableStateChanged, ...props }) {
// Transformes the table state to fetch query.
const tableQuery = transformCustomersStateToQuery(tableState);
// Fetch customers resource views and fields.
const { data: customersViews, isLoading: isViewsLoading } =
useResourceViews('customers');
// Fetch the customers resource fields.
const {
data: resourceMeta,
isLoading: isResourceMetaLoading,
isFetching: isResourceMetaFetching,
} = useResourceMeta('customers');
// Fetches customers data with pagination meta.
const {
data: { customers, pagination, filterMeta },
isLoading: isCustomersLoading,
isFetching: isCustomersFetching,
} = useCustomers(tableQuery, { keepPreviousData: true });
// Detarmines the datatable empty status.
const isEmptyStatus =
isEmpty(customers) && !isCustomersLoading && !tableStateChanged;
const state = {
customersViews,
customers,
pagination,
fields: getFieldsFromResourceMeta(resourceMeta.fields),
resourceMeta,
isResourceMetaLoading,
isResourceMetaFetching,
isViewsLoading,
isCustomersLoading,
isCustomersFetching,
isEmptyStatus,
};
return (
<DashboardInsider
loading={isViewsLoading || isResourceMetaLoading}
name={'customers-list'}
>
<CustomersListContext.Provider value={state} {...props} />
</DashboardInsider>
);
}
const useCustomersListContext = () => React.useContext(CustomersListContext);
export { CustomersListProvider, useCustomersListContext };

View File

@@ -0,0 +1,172 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import CustomersEmptyStatus from './CustomersEmptyStatus';
import { TABLES } from '@/constants/tables';
import {
DataTable,
DashboardContentTable,
TableSkeletonRows,
TableSkeletonHeader,
} from '@/components';
import { ActionsMenu, useCustomersTableColumns } from './components';
import withCustomers from './withCustomers';
import withCustomersActions from './withCustomersActions';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import withSettings from '@/containers/Settings/withSettings';
import { useCustomersListContext } from './CustomersListProvider';
import { useMemorizedColumnsWidths } from '@/hooks';
import { compose } from '@/utils';
/**
* Customers table.
*/
function CustomersTable({
// #withCustomersActions
setCustomersTableState,
// #withCustomers
customersTableState,
// #withAlerts
openAlert,
// #withDrawerActions
openDrawer,
// #withDialogActions
openDialog,
// #withSettings
customersTableSize,
}) {
const history = useHistory();
// Customers table columns.
const columns = useCustomersTableColumns();
// Customers list context.
const {
isEmptyStatus,
customers,
pagination,
isCustomersLoading,
isCustomersFetching,
} = useCustomersListContext();
// Local storage memorizing columns widths.
const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.CUSTOMERS);
// Handle fetch data once the page index, size or sort by of the table change.
const handleFetchData = React.useCallback(
({ pageSize, pageIndex, sortBy }) => {
setCustomersTableState({
pageIndex,
pageSize,
sortBy,
});
},
[setCustomersTableState],
);
// Handles the customer delete action.
const handleCustomerDelete = ({ id }) => {
openAlert('customer-delete', { contactId: id });
};
// Handle the customer edit action.
const handleCustomerEdit = (customer) => {
history.push(`/customers/${customer.id}/edit`);
};
const handleContactDuplicate = ({ id }) => {
openDialog('contact-duplicate', { contactId: id });
};
// Handle cancel/confirm inactive.
const handleInactiveCustomer = ({ id, contact_service }) => {
openAlert('customer-inactivate', {
customerId: id,
});
};
// Handle cancel/confirm activate.
const handleActivateCustomer = ({ id, contact_service }) => {
openAlert('customer-activate', {
customerId: id,
service: contact_service,
});
};
// Handle view detail contact.
const handleViewDetailCustomer = ({ id }) => {
openDrawer('customer-detail-drawer', { customerId: id });
};
// Handle cell click.
const handleCellClick = (cell, event) => {
openDrawer('customer-detail-drawer', { customerId: cell.row.original.id });
};
if (isEmptyStatus) {
return <CustomersEmptyStatus />;
}
return (
<DashboardContentTable>
<DataTable
noInitialFetch={true}
columns={columns}
data={customers}
loading={isCustomersLoading}
headerLoading={isCustomersLoading}
progressBarLoading={isCustomersFetching}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
spinnerProps={{ size: 30 }}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={customersTableSize}
payload={{
onDelete: handleCustomerDelete,
onEdit: handleCustomerEdit,
onDuplicate: handleContactDuplicate,
onInactivate: handleInactiveCustomer,
onActivate: handleActivateCustomer,
onViewDetails: handleViewDetailCustomer,
}}
ContextMenu={ActionsMenu}
/>
</DashboardContentTable>
);
}
export default compose(
withAlertsActions,
withDialogActions,
withCustomersActions,
withDrawerActions,
withCustomers(({ customersTableState }) => ({ customersTableState })),
withSettings(({ customersSettings }) => ({
customersTableSize: customersSettings?.tableSize,
})),
)(CustomersTable);

View File

@@ -0,0 +1,55 @@
// @ts-nocheck
import React from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { DashboardViewsTabs } from '@/components';
import { useCustomersListContext } from './CustomersListProvider';
import { compose, transfromViewsToTabs } from '@/utils';
import withCustomers from './withCustomers';
import withCustomersActions from './withCustomersActions';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
/**
* Customers views tabs.
*/
function CustomersViewsTabs({
// #withCustomersActions
setCustomersTableState,
// #withCustomers
customersCurrentView,
}) {
// Customers list context.
const { customersViews } = useCustomersListContext();
// Transformes the views to tabs.
const tabs = transfromViewsToTabs(customersViews);
// Handle tabs change.
const handleTabsChange = (viewSlug) => {
setCustomersTableState({ viewSlug: viewSlug || null });
};
return (
<Navbar className="navbar--dashboard-views">
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
currentViewSlug={customersCurrentView}
resourceName={'customers'}
tabs={tabs}
onChange={handleTabsChange}
/>
</NavbarGroup>
</Navbar>
);
}
export default compose(
withDashboardActions,
withCustomersActions,
withCustomers(({ customersTableState }) => ({
customersCurrentView: customersTableState.viewSlug,
})),
)(CustomersViewsTabs);

View File

@@ -0,0 +1,175 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import {
Menu,
MenuItem,
MenuDivider,
Intent,
Tooltip,
Position,
Classes,
} from '@blueprintjs/core';
import { Can, Icon, Money, If, AvaterCell } from '@/components';
import { CustomerAction, AbilitySubject } from '@/constants/abilityOption';
import { safeCallback } from '@/utils';
/**
* Actions menu.
*/
export function ActionsMenu({
row: { original },
payload: {
onEdit,
onDelete,
onDuplicate,
onInactivate,
onActivate,
onViewDetails,
},
}) {
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={intl.get('view_details')}
onClick={safeCallback(onViewDetails, original)}
/>
<Can I={CustomerAction.Edit} a={AbilitySubject.Customer}>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('edit_customer')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={CustomerAction.Create} a={AbilitySubject.Customer}>
<MenuItem
icon={<Icon icon="duplicate-16" />}
text={intl.get('duplicate')}
onClick={safeCallback(onDuplicate, original)}
/>
</Can>
<Can I={CustomerAction.Edit} a={AbilitySubject.Customer}>
<If condition={original.active}>
<MenuItem
text={intl.get('inactivate_customer')}
icon={<Icon icon="pause-16" iconSize={16} />}
onClick={safeCallback(onInactivate, original)}
/>
</If>
<If condition={!original.active}>
<MenuItem
text={intl.get('activate_customer')}
icon={<Icon icon="play-16" iconSize={16} />}
onClick={safeCallback(onActivate, original)}
/>
</If>
</Can>
<Can I={CustomerAction.Delete} a={AbilitySubject.Customer}>
<MenuDivider />
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_customer')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Can>
</Menu>
);
}
/**
* Phone number accessor.
*/
export function PhoneNumberAccessor(row) {
return <div className={'work_phone'}>{row.personal_phone}</div>;
}
/**
* Balance accessor.
*/
export function BalanceAccessor(row) {
return <Money amount={row.closing_balance} currency={row.currency_code} />;
}
/**
* Note column accessor.
*/
export function NoteAccessor(row) {
return (
<If condition={row.note}>
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.note}
position={Position.LEFT_TOP}
hoverOpenDelay={50}
>
<Icon icon={'file-alt'} iconSize={16} />
</Tooltip>
</If>
);
}
/**
* Retrieve customers table columns.
*/
export function useCustomersTableColumns() {
return useMemo(
() => [
{
id: 'avatar',
Header: '',
Cell: AvaterCell,
className: 'avatar',
width: 45,
disableResizing: true,
disableSortBy: true,
clickable: true,
},
{
id: 'display_name',
Header: intl.get('display_name'),
accessor: 'display_name',
className: 'display_name',
width: 150,
clickable: true,
},
{
id: 'company_name',
Header: intl.get('company_name'),
accessor: 'company_name',
className: 'company_name',
width: 150,
clickable: true,
},
{
id: 'work_phone',
Header: intl.get('phone_number'),
accessor: PhoneNumberAccessor,
className: 'phone_number',
width: 100,
clickable: true,
},
{
id: 'note',
Header: intl.get('note'),
accessor: NoteAccessor,
disableSortBy: true,
width: 85,
clickable: true,
},
{
id: 'balance',
Header: intl.get('receivable_balance'),
accessor: BalanceAccessor,
align: 'right',
width: 100,
clickable: true,
},
],
[],
);
}

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
import { transformTableStateToQuery } from '@/utils';
export const transformCustomersStateToQuery = (tableState) => {
return {
...transformTableStateToQuery(tableState),
inactive_mode: tableState.inactiveMode,
};
};

View File

@@ -0,0 +1,21 @@
// @ts-nocheck
import { connect } from 'react-redux';
import {
getCustomersTableStateFactory,
customersTableStateChangedFactory,
} from '@/store/customers/customers.selectors';
export default (mapState) => {
const getCustomersTableState = getCustomersTableStateFactory();
const customersTableStateChanged = customersTableStateChangedFactory();
const mapStateToProps = (state, props) => {
const mapped = {
customersTableState: getCustomersTableState(state, props),
customersTableStateChanged: customersTableStateChanged(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,13 @@
// @ts-nocheck
import { connect } from 'react-redux';
import {
setCustomersTableState,
resetCustomersTableState
} from '@/store/customers/customers.actions';
export const mapDispatchToProps = (dispatch) => ({
setCustomersTableState: (state) => dispatch(setCustomersTableState(state)),
resetCustomersTableState: () => dispatch(resetCustomersTableState()),
});
export default connect(null, mapDispatchToProps);

View File

@@ -0,0 +1,51 @@
// @ts-nocheck
import intl from 'react-intl-universal';
import { AbilitySubject, CustomerAction } from '@/constants/abilityOption';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
function CustomerUniversalSearchSelectComponent({
resourceType,
resourceId,
onAction,
// #withDrawerActions
openDrawer,
}) {
if (resourceType === RESOURCES_TYPES.CUSTOMER) {
openDrawer('customer-detail-drawer', { customerId: resourceId });
onAction && onAction();
}
return null;
}
const CustomerUniversalSearchSelectAction = withDrawerActions(
CustomerUniversalSearchSelectComponent,
);
/**
* Transformes customers to search.
* @param {*} contact
* @returns
*/
const customersToSearch = (contact) => ({
id: contact.id,
text: contact.display_name,
label: contact.formatted_balance,
reference: contact,
});
/**
* Binds universal search invoice configure.
*/
export const universalSearchCustomerBind = () => ({
resourceType: RESOURCES_TYPES.CUSTOMER,
optionItemLabel: intl.get('customers'),
selectItemAction: CustomerUniversalSearchSelectAction,
itemSelect: customersToSearch,
permission: {
ability: CustomerAction.View,
subject: AbilitySubject.Customer,
},
});

View File

@@ -0,0 +1,28 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from '@/components';
export const transformErrors = (errors) => {
if (errors.some((e) => e.type === 'CUSTOMER.HAS.SALES_INVOICES')) {
AppToaster.show({
message: intl.get('customer_has_sales_invoices'),
intent: Intent.DANGER,
});
}
if (
errors.find((error) => error.type === 'SOME.CUSTOMERS.HAVE.SALES_INVOICES')
) {
AppToaster.show({
message: intl.get('some_customers_have_sales_invoices'),
intent: Intent.DANGER,
});
}
if (errors.find((error) => error.type === 'CUSTOMER_HAS_TRANSACTIONS')) {
AppToaster.show({
message: intl.get('this_customer_cannot_be_deleted_as_it_is_associated_with_transactions'),
intent: Intent.DANGER,
});
}
};

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
import { connect } from 'react-redux';
import { getCustomerById } from '@/store/customers/customers.reducer';
const mapStateToProps = (state, props) => ({
customer: getCustomerById(state, props.customerId),
});
export default connect(mapStateToProps);