mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
Feat : Vendors
This commit is contained in:
@@ -83,8 +83,8 @@ export default [
|
|||||||
href: '/customers',
|
href: '/customers',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_customers'} />,
|
text: <T id={'vendors'} />,
|
||||||
href: '/customers/new',
|
href: '/vendors',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
65
client/src/containers/Vendors/Vendor.js
Normal file
65
client/src/containers/Vendors/Vendor.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
|
import VendorFrom from './VendorForm';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
|
||||||
|
import withVendorActions from './withVendorActions';
|
||||||
|
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function Vendor({
|
||||||
|
// #withVendorActions
|
||||||
|
requestFetchVendorsTable,
|
||||||
|
requsetFetchVendor,
|
||||||
|
|
||||||
|
// #wihtCurrenciesActions
|
||||||
|
requestFetchCurrencies,
|
||||||
|
}) {
|
||||||
|
const { id } = useParams();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
// Handle fetch Currencies data table
|
||||||
|
const fetchCurrencies = useQuery('currencies', () =>
|
||||||
|
requestFetchCurrencies(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle fetch vendors data table
|
||||||
|
const fetchVendors = useQuery('vendor-list', () =>
|
||||||
|
requestFetchVendorsTable({}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle fetch vendor details.
|
||||||
|
const fetchVendor = useQuery(
|
||||||
|
['vendor', id],
|
||||||
|
(_id, vendorId) => requsetFetchVendor(vendorId),
|
||||||
|
{ enabled: id && id },
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFormSubmit = useCallback(() => {}, []);
|
||||||
|
|
||||||
|
const handleCancel = useCallback(() => {
|
||||||
|
history.goBack();
|
||||||
|
}, [history]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider
|
||||||
|
loading={
|
||||||
|
fetchCurrencies.isFetching ||
|
||||||
|
fetchVendors.isFetching ||
|
||||||
|
fetchVendor.isFetching
|
||||||
|
}
|
||||||
|
name={'vendor-form'}
|
||||||
|
>
|
||||||
|
<VendorFrom
|
||||||
|
onFormSubmit={handleFormSubmit}
|
||||||
|
vendorId={id}
|
||||||
|
onCancelForm={handleCancel}
|
||||||
|
/>
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withCurrenciesActions, withVendorActions)(Vendor);
|
||||||
80
client/src/containers/Vendors/VendorActionsBar.js
Normal file
80
client/src/containers/Vendors/VendorActionsBar.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
NavbarGroup,
|
||||||
|
NavbarDivider,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
Intent,
|
||||||
|
Popover,
|
||||||
|
Position,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
import { DashboardActionViewsList } from 'components';
|
||||||
|
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function VendorActionsBar({
|
||||||
|
// #ownProps
|
||||||
|
selectedRows = [],
|
||||||
|
}) {
|
||||||
|
const [filterCount, setFilterCount] = useState(0);
|
||||||
|
const history = useHistory();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const onClickNewVendor = useCallback(() => {
|
||||||
|
history.push('/vendors/new');
|
||||||
|
}, [history]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<DashboardActionViewsList resourceName={'vendors'} views={[]} />
|
||||||
|
<NavbarDivider />
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon={'plus'} />}
|
||||||
|
text={<T id={'new_vendor'} />}
|
||||||
|
onClick={onClickNewVendor}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
<Popover
|
||||||
|
// content={}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
|
text={
|
||||||
|
filterCount <= 0 ? (
|
||||||
|
<T id={'filter'} />
|
||||||
|
) : (
|
||||||
|
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
<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'} />}
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VendorActionsBar;
|
||||||
20
client/src/containers/Vendors/VendorAttahmentTab.js
Normal file
20
client/src/containers/Vendors/VendorAttahmentTab.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Dragzone from 'components/Dragzone';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor Attahment Tab.
|
||||||
|
*/
|
||||||
|
function VendorAttahmentTab() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Dragzone
|
||||||
|
initialFiles={[]}
|
||||||
|
onDrop={null}
|
||||||
|
onDeleteFile={[]}
|
||||||
|
hint={'Attachments: Maxiumum size: 20MB'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VendorAttahmentTab;
|
||||||
108
client/src/containers/Vendors/VendorFinanicalPanelTab.js
Normal file
108
client/src/containers/Vendors/VendorFinanicalPanelTab.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { FormGroup, Position, Classes } from '@blueprintjs/core';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { FastField, ErrorMessage } from 'formik';
|
||||||
|
import { MoneyInputGroup, CurrencySelectList, Row, Col } from 'components';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
|
||||||
|
import withCurrencies from 'containers/Currencies/withCurrencies';
|
||||||
|
|
||||||
|
import {
|
||||||
|
compose,
|
||||||
|
momentFormatter,
|
||||||
|
tansformDateValue,
|
||||||
|
inputIntent,
|
||||||
|
} from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor Finaniceal Panel Tab.
|
||||||
|
*/
|
||||||
|
function VendorFinanicalPanelTab({
|
||||||
|
// #withCurrencies
|
||||||
|
currenciesList,
|
||||||
|
|
||||||
|
// #OwnProps
|
||||||
|
vendorId,
|
||||||
|
}) {
|
||||||
|
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')}
|
||||||
|
value={tansformDateValue(value)}
|
||||||
|
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||||
|
disabled={vendorId}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
{/*------------ Opening balance -----------*/}
|
||||||
|
<FastField name={'opening_balance'}>
|
||||||
|
{({ 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}
|
||||||
|
>
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
prefix={'$'}
|
||||||
|
inputGroupProps={{
|
||||||
|
fill: true,
|
||||||
|
...field,
|
||||||
|
}}
|
||||||
|
disabled={vendorId}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
{/*------------ 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={currenciesList}
|
||||||
|
selectedCurrencyCode={value}
|
||||||
|
onCurrencySelected={(currency) => {
|
||||||
|
form.setFieldValue('currency_code', currency.currency_code);
|
||||||
|
}}
|
||||||
|
disabled={vendorId}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withCurrencies(({ currenciesList }) => ({ currenciesList })),
|
||||||
|
)(VendorFinanicalPanelTab);
|
||||||
50
client/src/containers/Vendors/VendorFloatingActions.js
Normal file
50
client/src/containers/Vendors/VendorFloatingActions.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent, Button } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { saveInvoke } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
|
export default function VendorFloatingActions({
|
||||||
|
onSubmitClick,
|
||||||
|
onSubmitAndNewClick,
|
||||||
|
onCancelClick,
|
||||||
|
isSubmitting,
|
||||||
|
vendor,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
type="submit"
|
||||||
|
onClick={(event) => {
|
||||||
|
saveInvoke(onSubmitClick, event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{vendor ? <T id={'edit'} /> : <T id={'save'} />}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
className={'ml1'}
|
||||||
|
name={'save_and_new'}
|
||||||
|
type="submit"
|
||||||
|
onClick={(event) => {
|
||||||
|
saveInvoke(onSubmitAndNewClick, event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<T id={'save_new'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={'ml1'}
|
||||||
|
onClick={(event) => {
|
||||||
|
saveInvoke(onCancelClick, event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
176
client/src/containers/Vendors/VendorForm.js
Normal file
176
client/src/containers/Vendors/VendorForm.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { Formik, Form } from 'formik';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import AppToaster from 'components/AppToaster';
|
||||||
|
import {
|
||||||
|
CreateVendorFormSchema,
|
||||||
|
EditVendorFormSchema,
|
||||||
|
} from './VendorForm.schema';
|
||||||
|
|
||||||
|
import VendorFormPrimarySection from './VendorFormPrimarySection';
|
||||||
|
import VendorFormAfterPrimarySection from './VendorFormAfterPrimarySection';
|
||||||
|
import VendorTabs from './VendorsTabs';
|
||||||
|
import VendorFloatingActions from './VendorFloatingActions';
|
||||||
|
|
||||||
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
import withVendorDetail from './withVendorDetail';
|
||||||
|
import withVendorActions from './withVendorActions';
|
||||||
|
|
||||||
|
import { compose, transformToForm } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
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'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor form.
|
||||||
|
*/
|
||||||
|
function VendorForm({
|
||||||
|
// #withDashboardActions
|
||||||
|
changePageTitle,
|
||||||
|
|
||||||
|
// #withVendorDetailsActions
|
||||||
|
vendor,
|
||||||
|
// #withVendorActions
|
||||||
|
requestSubmitVendor,
|
||||||
|
requestEditVendor,
|
||||||
|
|
||||||
|
// #OwnProps
|
||||||
|
vendorId,
|
||||||
|
}) {
|
||||||
|
const isNewMode = !vendorId;
|
||||||
|
const [submitPayload, setSubmitPayload] = useState({});
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial values in create and edit mode.
|
||||||
|
*/
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
...defaultInitialValues,
|
||||||
|
...transformToForm(vendor, defaultInitialValues),
|
||||||
|
}),
|
||||||
|
[defaultInitialValues],
|
||||||
|
);
|
||||||
|
console.log(isNewMode, 'Val');
|
||||||
|
useEffect(() => {
|
||||||
|
!isNewMode
|
||||||
|
? changePageTitle(formatMessage({ id: 'edit_vendor' }))
|
||||||
|
: changePageTitle(formatMessage({ id: 'new_vendor' }));
|
||||||
|
}, [changePageTitle, isNewMode, formatMessage]);
|
||||||
|
|
||||||
|
//Handles the form submit.
|
||||||
|
const handleFormSubmit = (
|
||||||
|
values,
|
||||||
|
{ setSubmitting: resetForm, setErrors },
|
||||||
|
) => {
|
||||||
|
const requestForm = { ...values };
|
||||||
|
|
||||||
|
const onSuccess = () => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: isNewMode
|
||||||
|
? 'the_vendor_has_been_successfully_created'
|
||||||
|
: 'the_item_vendor_has_been_successfully_edited',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitPayload(false);
|
||||||
|
resetForm();
|
||||||
|
|
||||||
|
if (!submitPayload.noRedirect) {
|
||||||
|
history.push('/vendors');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = () => {
|
||||||
|
setSubmitPayload(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vendor && vendor.id) {
|
||||||
|
requestEditVendor(vendor.id, requestForm).then(onSuccess).catch(onError);
|
||||||
|
} else {
|
||||||
|
requestSubmitVendor(requestForm).then(onSuccess).catch(onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelClick = useCallback(() => {
|
||||||
|
history.goBack();
|
||||||
|
}, [history]);
|
||||||
|
|
||||||
|
const handleSubmitAndNewClick = useCallback(() => {
|
||||||
|
setSubmitPayload({ noRedirect: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}>
|
||||||
|
<Formik
|
||||||
|
validationSchema={
|
||||||
|
isNewMode ? CreateVendorFormSchema : EditVendorFormSchema
|
||||||
|
}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
>
|
||||||
|
{({ isSubmitting }) => (
|
||||||
|
<Form>
|
||||||
|
<VendorFormPrimarySection />
|
||||||
|
<VendorFormAfterPrimarySection />
|
||||||
|
<VendorTabs vendor={vendorId} />
|
||||||
|
<VendorFloatingActions
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
vendor={vendorId}
|
||||||
|
onCancelClick={handleCancelClick}
|
||||||
|
onSubmitAndNewClick={handleSubmitAndNewClick}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withVendorDetail(),
|
||||||
|
withDashboardActions,
|
||||||
|
withVendorActions,
|
||||||
|
)(VendorForm);
|
||||||
44
client/src/containers/Vendors/VendorForm.schema.js
Normal file
44
client/src/containers/Vendors/VendorForm.schema.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import { formatMessage } from 'services/intl';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
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(formatMessage({ id: '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(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateVendorFormSchema = Schema;
|
||||||
|
export const EditVendorFormSchema = Schema;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core';
|
||||||
|
import { FastField, ErrorMessage } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor form after primary section.
|
||||||
|
*/
|
||||||
|
function VendorFormAfterPrimarySection() {
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
|
||||||
|
{/*------------ Vendor email -----------*/}
|
||||||
|
<FastField name={'email'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'email'} />}
|
||||||
|
className={'form-group--email'}
|
||||||
|
label={<T id={'vendor_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={'work_phone'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
placeholder={'Work'}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
<FastField name={'personal_phone'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
placeholder={'Mobile'}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
{/*------------ Vendor 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 {...field} />
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VendorFormAfterPrimarySection;
|
||||||
124
client/src/containers/Vendors/VendorFormPrimarySection.js
Normal file
124
client/src/containers/Vendors/VendorFormPrimarySection.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import {
|
||||||
|
Hint,
|
||||||
|
FieldRequiredHint,
|
||||||
|
SalutationList,
|
||||||
|
DisplayNameList,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor form primary section.
|
||||||
|
*/
|
||||||
|
function VendorFormPrimarySection() {
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
||||||
|
{/**----------- Vendor 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={'First Name'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
className={classNames('input-group--first-name')}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<FastField name={'last_name'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<InputGroup
|
||||||
|
placeholder={'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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VendorFormPrimarySection;
|
||||||
57
client/src/containers/Vendors/VendorViewsTabs.js
Normal file
57
client/src/containers/Vendors/VendorViewsTabs.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||||
|
import { compose } from 'redux';
|
||||||
|
import { useParams, withRouter, useHistory } from 'react-router-dom';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { DashboardViewsTabs } from 'components';
|
||||||
|
|
||||||
|
import withVendors from './withVendors';
|
||||||
|
import withVendorActions from './withVendorActions';
|
||||||
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customers views tabs.
|
||||||
|
*/
|
||||||
|
function VendorViewsTabs({
|
||||||
|
// #withViewDetail
|
||||||
|
viewId,
|
||||||
|
viewItem,
|
||||||
|
|
||||||
|
// #withVendors
|
||||||
|
vendorViews,
|
||||||
|
|
||||||
|
// #withDashboardActions
|
||||||
|
setTopbarEditView,
|
||||||
|
changePageSubtitle,
|
||||||
|
}) {
|
||||||
|
const { custom_view_id: customViewId = null } = useParams();
|
||||||
|
|
||||||
|
const tabs = useMemo(() =>
|
||||||
|
vendorViews.map(
|
||||||
|
(view) => ({
|
||||||
|
...pick(view, ['name', 'id']),
|
||||||
|
}),
|
||||||
|
[vendorViews],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Navbar className="navbar--dashboard-views">
|
||||||
|
<NavbarGroup align={Alignment.LEFT}>
|
||||||
|
<DashboardViewsTabs
|
||||||
|
initialViewId={customViewId}
|
||||||
|
resourceName={'vendors'}
|
||||||
|
tabs={tabs}
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</Navbar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
withDashboardActions,
|
||||||
|
withVendors(({ vendorViews }) => ({ vendorViews })),
|
||||||
|
)(VendorViewsTabs);
|
||||||
161
client/src/containers/Vendors/VendorsList.js
Normal file
161
client/src/containers/Vendors/VendorsList.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||||
|
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
FormattedHTMLMessage,
|
||||||
|
useIntl,
|
||||||
|
} from 'react-intl';
|
||||||
|
|
||||||
|
import AppToaster from 'components/AppToaster';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
|
|
||||||
|
import VendorsTable from './VendorsTable';
|
||||||
|
import VendorActionsBar from './VendorActionsBar';
|
||||||
|
import VendorsViewsTabs from './VendorViewsTabs';
|
||||||
|
|
||||||
|
import withVendors from './withVendors';
|
||||||
|
import withVendorActions from './withVendorActions';
|
||||||
|
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||||
|
import withViewsActions from 'containers/Views/withViewsActions';
|
||||||
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function VendorsList({
|
||||||
|
// #withDashboardActions
|
||||||
|
changePageTitle,
|
||||||
|
|
||||||
|
// #withVendors
|
||||||
|
vendorTableQuery,
|
||||||
|
|
||||||
|
// #withVendorActions
|
||||||
|
requestDeleteVender,
|
||||||
|
requestFetchVendorsTable,
|
||||||
|
addVendorsTableQueries,
|
||||||
|
}) {
|
||||||
|
const [deleteVendor, setDeleteVendor] = useState(false);
|
||||||
|
const [selectedRows, setSelectedRows] = useState([]);
|
||||||
|
const [tableLoading, setTableLoading] = useState(false);
|
||||||
|
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
changePageTitle(formatMessage({ id: 'vendors_list' }));
|
||||||
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
|
// Handle fetch customers data table
|
||||||
|
const fetchVendors = useQuery(['vendors-table', vendorTableQuery], () =>
|
||||||
|
requestFetchVendorsTable(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle Edit vendor data table
|
||||||
|
const handleEditVendor = useCallback(
|
||||||
|
(vendor) => {
|
||||||
|
history.push(`/vendors/${vendor.id}/edit`);
|
||||||
|
},
|
||||||
|
[history],
|
||||||
|
);
|
||||||
|
// Handle click delete vendor.
|
||||||
|
const handleDeleteVendor = useCallback(
|
||||||
|
(vendor) => {
|
||||||
|
setDeleteVendor(vendor);
|
||||||
|
},
|
||||||
|
[setDeleteVendor],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle cancel delete the vendor.
|
||||||
|
const handleCancelDeleteVendor = useCallback(() => {
|
||||||
|
setDeleteVendor(false);
|
||||||
|
}, [setDeleteVendor]);
|
||||||
|
|
||||||
|
// Transform API errors in toasts messages.
|
||||||
|
const transformErrors = useCallback((errors) => {
|
||||||
|
if (errors.some((e) => e.type === 'VENDOR.HAS.BILLS')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'vendor_has_bills',
|
||||||
|
}),
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// handle confirm delete vendor.
|
||||||
|
const handleConfirmDeleteVendor = useCallback(() => {
|
||||||
|
requestDeleteVender(deleteVendor.id)
|
||||||
|
.then(() => {
|
||||||
|
setDeleteVendor(false);
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_vendor_has_been_successfully_deleted',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
setDeleteVendor(false);
|
||||||
|
transformErrors(errors);
|
||||||
|
});
|
||||||
|
}, [requestDeleteVender, deleteVendor, formatMessage]);
|
||||||
|
|
||||||
|
// Handle selected rows change.
|
||||||
|
const handleSelectedRowsChange = useCallback(
|
||||||
|
(vendor) => {
|
||||||
|
setSelectedRows(vendor);
|
||||||
|
},
|
||||||
|
[setSelectedRows],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tableLoading && !fetchVendors.isFetching) {
|
||||||
|
setTableLoading(false);
|
||||||
|
}
|
||||||
|
}, [tableLoading, fetchVendors]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider name={'vendors-list'}>
|
||||||
|
<VendorActionsBar selectedRows={selectedRows} />
|
||||||
|
<DashboardPageContent>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact={true}
|
||||||
|
// path={}
|
||||||
|
>
|
||||||
|
<VendorsViewsTabs />
|
||||||
|
<VendorsTable
|
||||||
|
loading={fetchVendors.isFetching}
|
||||||
|
onDeleteVendor={handleDeleteVendor}
|
||||||
|
onEditVendor={handleEditVendor}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={deleteVendor}
|
||||||
|
onCancel={handleCancelDeleteVendor}
|
||||||
|
onConfirm={handleConfirmDeleteVendor}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id={'once_delete_this_vendor_you_will_able_to_restore_it'}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
</DashboardPageContent>
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withVendorActions,
|
||||||
|
withDashboardActions,
|
||||||
|
withVendors(({ vendorTableQuery }) => ({ vendorTableQuery })),
|
||||||
|
)(VendorsList);
|
||||||
226
client/src/containers/Vendors/VendorsTable.js
Normal file
226
client/src/containers/Vendors/VendorsTable.js
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Popover,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuDivider,
|
||||||
|
Position,
|
||||||
|
Intent,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { useIsValuePassed } from 'hooks';
|
||||||
|
|
||||||
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
|
import { DataTable, Icon, Money } from 'components';
|
||||||
|
|
||||||
|
import withVendors from './withVendors';
|
||||||
|
import withVendorsActions from './withVendorActions';
|
||||||
|
|
||||||
|
import { compose, firstLettersArgs, saveInvoke } from 'utils';
|
||||||
|
|
||||||
|
const AvatarCell = (row) => {
|
||||||
|
return <span className="avatar">{firstLettersArgs(row.display_name)}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function VendorsTable({
|
||||||
|
// #withVendors
|
||||||
|
vendorsCurrentPage,
|
||||||
|
vendorsLoading,
|
||||||
|
vendorsPageination,
|
||||||
|
vendorTableQuery,
|
||||||
|
vendorItems,
|
||||||
|
|
||||||
|
// #withVendorsActions
|
||||||
|
addVendorsTableQueries,
|
||||||
|
|
||||||
|
// #OwnProps
|
||||||
|
loading,
|
||||||
|
onEditVendor,
|
||||||
|
onDeleteVendor,
|
||||||
|
onSelectedRowsChange,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const isLoadedBefore = useIsValuePassed(vendorsLoading, false);
|
||||||
|
|
||||||
|
// Vendor actions list.
|
||||||
|
const renderContextMenu = useMemo(
|
||||||
|
() => ({ vendor, onEditVendor, onDeleteVendor }) => {
|
||||||
|
const handleEditVendor = () => {
|
||||||
|
saveInvoke(onEditVendor, vendor);
|
||||||
|
};
|
||||||
|
const handleDeleteVendor = () => {
|
||||||
|
saveInvoke(onDeleteVendor, vendor);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="reader-18" />}
|
||||||
|
text={formatMessage({ id: 'view_details' })}
|
||||||
|
/>
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="pen-18" />}
|
||||||
|
text={formatMessage({ id: 'edit_vendor' })}
|
||||||
|
onClick={handleEditVendor}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
text={formatMessage({ id: 'delete_vendor' })}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={handleDeleteVendor}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[formatMessage],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Renders actions table cell.
|
||||||
|
const renderActionsCell = useMemo(
|
||||||
|
() => ({ cell }) => (
|
||||||
|
<Popover
|
||||||
|
content={renderContextMenu({
|
||||||
|
vendor: cell.row.original,
|
||||||
|
onEditVendor,
|
||||||
|
onDeleteVendor,
|
||||||
|
})}
|
||||||
|
position={Position.RIGHT_BOTTOM}
|
||||||
|
>
|
||||||
|
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||||
|
</Popover>
|
||||||
|
),
|
||||||
|
[onDeleteVendor, onEditVendor, renderContextMenu],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Table columns.
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'avatar',
|
||||||
|
Header: '',
|
||||||
|
accessor: AvatarCell,
|
||||||
|
className: 'avatar',
|
||||||
|
width: 50,
|
||||||
|
disableResizing: true,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'display_name',
|
||||||
|
Header: formatMessage({ id: 'display_name' }),
|
||||||
|
accessor: 'display_name',
|
||||||
|
className: 'display_name',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'company_name',
|
||||||
|
Header: formatMessage({ id: 'company_name' }),
|
||||||
|
accessor: 'company_name',
|
||||||
|
className: 'company_name',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'phone_number',
|
||||||
|
Header: formatMessage({ id: 'phone_number' }),
|
||||||
|
accessor: (row) => (
|
||||||
|
<div>
|
||||||
|
<div className={'work_phone'}>{row.work_phone}</div>
|
||||||
|
<div className={'personal_phone'}>{row.personal_phone}</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
className: 'phone_number',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'receivable_balance',
|
||||||
|
Header: formatMessage({ id: 'receivable_balance' }),
|
||||||
|
accessor: (r) => <Money amount={r.closing_balance} currency={'USD'} />,
|
||||||
|
className: 'receivable_balance',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
Cell: renderActionsCell,
|
||||||
|
className: 'actions',
|
||||||
|
width: 70,
|
||||||
|
disableResizing: true,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[formatMessage, renderActionsCell],
|
||||||
|
);
|
||||||
|
|
||||||
|
//Handle fetch data table
|
||||||
|
const handleFetchData = useCallback(
|
||||||
|
({ pageIndex, pageSize, sortBy }) => {
|
||||||
|
addVendorsTableQueries({
|
||||||
|
page: pageIndex + 1,
|
||||||
|
page_size: pageSize,
|
||||||
|
...(sortBy.length > 0
|
||||||
|
? {
|
||||||
|
column_sort_order: sortBy[0].id,
|
||||||
|
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[addVendorsTableQueries],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectedRowsChange = useCallback(
|
||||||
|
(selectedRows) => {
|
||||||
|
onSelectedRowsChange &&
|
||||||
|
onSelectedRowsChange(selectedRows.map((s) => s.original));
|
||||||
|
},
|
||||||
|
[onSelectedRowsChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const rowContextMenu = (cell) =>
|
||||||
|
renderContextMenu({
|
||||||
|
vendor: cell.row.original,
|
||||||
|
onEditVendor,
|
||||||
|
onDeleteVendor,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(vendorsCurrentPage, 'vendorsCurrentPage');
|
||||||
|
return (
|
||||||
|
<LoadingIndicator loading={vendorsLoading && !isLoadedBefore} mount={false}>
|
||||||
|
<DataTable
|
||||||
|
noInitialFetch={true}
|
||||||
|
columns={columns}
|
||||||
|
data={vendorItems}
|
||||||
|
onFetchData={handleFetchData}
|
||||||
|
selectionColumn={true}
|
||||||
|
expandable={false}
|
||||||
|
sticky={true}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
spinnerProps={{ size: 30 }}
|
||||||
|
rowContextMenu={rowContextMenu}
|
||||||
|
pagination={true}
|
||||||
|
manualSortBy={true}
|
||||||
|
pagesCount={vendorsPageination.pagesCount}
|
||||||
|
autoResetSortBy={false}
|
||||||
|
autoResetPage={false}
|
||||||
|
initialPageSize={vendorTableQuery.page_size}
|
||||||
|
initialPageIndex={vendorTableQuery.page - 1}
|
||||||
|
/>
|
||||||
|
</LoadingIndicator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withVendors(
|
||||||
|
({
|
||||||
|
vendorItems,
|
||||||
|
vendorsLoading,
|
||||||
|
vendorTableQuery,
|
||||||
|
vendorsPageination,
|
||||||
|
}) => ({
|
||||||
|
vendorItems,
|
||||||
|
vendorsLoading,
|
||||||
|
vendorsPageination,
|
||||||
|
vendorTableQuery,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
withVendorsActions,
|
||||||
|
)(VendorsTable);
|
||||||
44
client/src/containers/Vendors/VendorsTabs.js
Normal file
44
client/src/containers/Vendors/VendorsTabs.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tabs, Tab } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import VendorFinanicalPanelTab from './VendorFinanicalPanelTab';
|
||||||
|
import VendorAttahmentTab from './VendorAttahmentTab';
|
||||||
|
import CustomerAddressTabs from 'containers/Customers/CustomerAddressTabs';
|
||||||
|
import CustomerNotePanel from 'containers/Customers/CustomerNotePanel';
|
||||||
|
|
||||||
|
export default function VendorTabs({ vendor }) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
||||||
|
<Tabs
|
||||||
|
animate={true}
|
||||||
|
id={'vendor-tabs'}
|
||||||
|
large={true}
|
||||||
|
defaultSelectedTabId="financial"
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
id={'financial'}
|
||||||
|
title={formatMessage({ id: 'financial_details' })}
|
||||||
|
panel={<VendorFinanicalPanelTab vendorId={vendor} />}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
id={'address'}
|
||||||
|
title={formatMessage({ id: 'address' })}
|
||||||
|
panel={<CustomerAddressTabs />}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
id="notes"
|
||||||
|
title={formatMessage({ id: 'notes' })}
|
||||||
|
panel={<CustomerNotePanel />}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
id={'attachement'}
|
||||||
|
title={formatMessage({ id: 'attachement' })}
|
||||||
|
panel={<VendorAttahmentTab />}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,17 +4,17 @@ import {
|
|||||||
editVendor,
|
editVendor,
|
||||||
deleteVendor,
|
deleteVendor,
|
||||||
fetchVendorsTable,
|
fetchVendorsTable,
|
||||||
|
fetchVendor,
|
||||||
} from 'store/vendors/vendors.actions';
|
} from 'store/vendors/vendors.actions';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
|
|
||||||
const mapDipatchToProps = (dispatch) => ({
|
const mapDipatchToProps = (dispatch) => ({
|
||||||
requestSubmitVendor: (form) => dispatch(submitVendor({ form })),
|
requestSubmitVendor: (form) => dispatch(submitVendor({ form })),
|
||||||
requestEditVendor: (id, form) => dispatch(editVendor(id, form)),
|
requestEditVendor: (id, form) => dispatch(editVendor({ id, form })),
|
||||||
|
requsetFetchVendor: (id) => dispatch(fetchVendor({ id })),
|
||||||
requestFetchVendorsTable: (query = {}) =>
|
requestFetchVendorsTable: (query = {}) =>
|
||||||
dispatch(fetchVendorsTable({ query: { ...query } })),
|
dispatch(fetchVendorsTable({ query: { ...query } })),
|
||||||
requestDeleteVender: (id) => dispatch(deleteVendor({ id })),
|
requestDeleteVender: (id) => dispatch(deleteVendor({ id })),
|
||||||
|
|
||||||
changeVendorView: (id) =>
|
changeVendorView: (id) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.VENDORS_SET_CURRENT_VIEW,
|
type: t.VENDORS_SET_CURRENT_VIEW,
|
||||||
@@ -29,4 +29,3 @@ const mapDipatchToProps = (dispatch) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDipatchToProps);
|
export default connect(null, mapDipatchToProps);
|
||||||
|
|
||||||
|
|||||||
10
client/src/containers/Vendors/withVendorDetail.js
Normal file
10
client/src/containers/Vendors/withVendorDetail.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { getVendorByIdFactory } from 'store/vendors/vendors.selectors';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const getVendorById = getVendorByIdFactory();
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
vendor: getVendorById(state, props),
|
||||||
|
});
|
||||||
|
return connect(mapStateToProps);
|
||||||
|
};
|
||||||
@@ -819,4 +819,22 @@ export default {
|
|||||||
the_name_used_before: 'The name is already used.',
|
the_name_used_before: 'The name is already used.',
|
||||||
the_item_has_associated_transactions: 'The item has associated transactions.',
|
the_item_has_associated_transactions: 'The item has associated transactions.',
|
||||||
customer_has_sales_invoices: 'Customer has sales invoices',
|
customer_has_sales_invoices: 'Customer has sales invoices',
|
||||||
};
|
vendors: 'Vendors',
|
||||||
|
vendor_email: 'Vendor Email',
|
||||||
|
new_vendor: 'New Vendor',
|
||||||
|
edit_vendor: 'Edit Vendor',
|
||||||
|
delete_vendor: 'Delete Vendor',
|
||||||
|
vendors_list: 'Vendors List',
|
||||||
|
the_vendor_has_been_successfully_created:
|
||||||
|
'The vendor has been successfully created.',
|
||||||
|
the_vendor_has_been_successfully_deleted:
|
||||||
|
'The vendor has been successfully deleted.',
|
||||||
|
the_vendors_has_been_successfully_deleted:
|
||||||
|
'The vendors has been successfully deleted.',
|
||||||
|
the_item_vendor_has_been_successfully_edited:
|
||||||
|
'The item vendor has been successfully edited.',
|
||||||
|
once_delete_this_vendor_you_will_able_to_restore_it: `Once you delete this vendor, you won\'t be able to restore it later. Are you sure you want to delete this vendor?`,
|
||||||
|
once_delete_these_vendors_you_will_not_able_restore_them:
|
||||||
|
"Once you delete these vendors, you won't be able to retrieve them later. Are you sure you want to delete them?",
|
||||||
|
vendor_has_bills:'Vendor has bills'
|
||||||
|
};
|
||||||
|
|||||||
@@ -205,6 +205,29 @@ export default [
|
|||||||
breadcrumb: 'Customers',
|
breadcrumb: 'Customers',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Vendors
|
||||||
|
{
|
||||||
|
path: `/vendors/:id/edit`,
|
||||||
|
component: LazyLoader({
|
||||||
|
loader: () => import('containers/Vendors/Vendor'),
|
||||||
|
}),
|
||||||
|
breadcrumb: 'Edit Vendor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `/vendors/new`,
|
||||||
|
component: LazyLoader({
|
||||||
|
loader: () => import('containers/Vendors/Vendor'),
|
||||||
|
}),
|
||||||
|
breadcrumb: 'New Vendor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `/vendors`,
|
||||||
|
component: LazyLoader({
|
||||||
|
loader: () => import('containers/Vendors/VendorsList'),
|
||||||
|
}),
|
||||||
|
breadcrumb: 'Vendors',
|
||||||
|
},
|
||||||
|
|
||||||
//Estimates
|
//Estimates
|
||||||
{
|
{
|
||||||
path: `/estimates/:id/edit`,
|
path: `/estimates/:id/edit`,
|
||||||
@@ -280,14 +303,16 @@ export default [
|
|||||||
{
|
{
|
||||||
path: `/payment-receive/:id/edit`,
|
path: `/payment-receive/:id/edit`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
loader: () => import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
loader: () =>
|
||||||
|
import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
||||||
}),
|
}),
|
||||||
breadcrumb: 'Edit',
|
breadcrumb: 'Edit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/payment-receive/new`,
|
path: `/payment-receive/new`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
loader: () => import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
loader: () =>
|
||||||
|
import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
||||||
}),
|
}),
|
||||||
breadcrumb: 'New Payment Receive',
|
breadcrumb: 'New Payment Receive',
|
||||||
},
|
},
|
||||||
@@ -355,8 +380,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: `/payment-mades`,
|
path: `/payment-mades`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
loader: () =>
|
loader: () => import('containers/Purchases/PaymentMades/PaymentMadeList'),
|
||||||
import('containers/Purchases/PaymentMades/PaymentMadeList'),
|
|
||||||
}),
|
}),
|
||||||
breadcrumb: 'Payment Made List',
|
breadcrumb: 'Payment Made List',
|
||||||
},
|
},
|
||||||
|
|||||||
30
client/src/store/vendors/vendors.actions.js
vendored
30
client/src/store/vendors/vendors.actions.js
vendored
@@ -9,15 +9,14 @@ export const fetchVendorsTable = ({ query }) => {
|
|||||||
type: t.VENDORS_TABLE_LOADING,
|
type: t.VENDORS_TABLE_LOADING,
|
||||||
payload: { loading: true },
|
payload: { loading: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
ApiService.get(`vendors`, { params: { ...pageQuery, ...query } })
|
ApiService.get(`vendors`, { params: { ...pageQuery, ...query } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.VENDORS_PAGE_SET,
|
type: t.VENDORS_PAGE_SET,
|
||||||
payload: {
|
payload: {
|
||||||
vendors: response.data.vendors,
|
vendors: response.data.vendors,
|
||||||
pagination: response.data.pagination,
|
|
||||||
customViewId: response.data.customViewId || -1,
|
customViewId: response.data.customViewId || -1,
|
||||||
|
paginationMeta: response.data.pagination,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -37,7 +36,6 @@ export const fetchVendorsTable = ({ query }) => {
|
|||||||
type: t.VENDORS_TABLE_LOADING,
|
type: t.VENDORS_TABLE_LOADING,
|
||||||
payload: { loading: false },
|
payload: { loading: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -81,18 +79,32 @@ export const submitVendor = ({ form }) => {
|
|||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
ApiService.post('vendors', form)
|
ApiService.post('vendors', form)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
|
||||||
});
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const { response } = error;
|
const { response } = error;
|
||||||
const { data } = response;
|
const { data } = response;
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
|
||||||
});
|
|
||||||
reject(data?.errors);
|
reject(data?.errors);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchVendor = ({ id }) => {
|
||||||
|
return (dispatch) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
ApiService.get(`vendors/${id}`)
|
||||||
|
.then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.VENDOR_SET,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
vendor: response.data.vendor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
resolve(response);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
61
client/src/store/vendors/vendors.reducer.js
vendored
61
client/src/store/vendors/vendors.reducer.js
vendored
@@ -1,5 +1,8 @@
|
|||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import { createTableQueryReducers } from 'store/queryReducers';
|
import {
|
||||||
|
viewPaginationSetReducer,
|
||||||
|
createTableQueryReducers,
|
||||||
|
} from 'store/journalNumber.reducer';
|
||||||
|
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
@@ -7,19 +10,23 @@ const initialState = {
|
|||||||
items: {},
|
items: {},
|
||||||
views: {},
|
views: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
|
currentViewId: -1,
|
||||||
|
|
||||||
tableQuery: {
|
tableQuery: {
|
||||||
page_size: 5,
|
page_size: 5,
|
||||||
page: 1,
|
page: 1,
|
||||||
},
|
},
|
||||||
currentViewId: -1,
|
|
||||||
};
|
};
|
||||||
|
export default createReducer(initialState, {
|
||||||
const reducer = createReducer(initialState, {
|
[t.VENDOR_SET]: (state, action) => {
|
||||||
|
const { id, vendor } = action.payload;
|
||||||
|
const _vendors = state.items[id] || {};
|
||||||
|
state.items[id] = { ..._vendors, ...vendor };
|
||||||
|
},
|
||||||
[t.VENDORS_TABLE_LOADING]: (state, action) => {
|
[t.VENDORS_TABLE_LOADING]: (state, action) => {
|
||||||
const { loading } = action.payload;
|
const { loading } = action.payload;
|
||||||
state.loading = loading;
|
state.loading = loading;
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.VENDORS_ITEMS_SET]: (state, action) => {
|
[t.VENDORS_ITEMS_SET]: (state, action) => {
|
||||||
const { vendors } = action.payload;
|
const { vendors } = action.payload;
|
||||||
const _vendors = {};
|
const _vendors = {};
|
||||||
@@ -33,24 +40,21 @@ const reducer = createReducer(initialState, {
|
|||||||
..._vendors,
|
..._vendors,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.VENDORS_PAGE_SET]: (state, action) => {
|
[t.VENDORS_PAGE_SET]: (state, action) => {
|
||||||
const { customViewId, vendors, pagination } = action.payload;
|
const { customViewId, vendors, paginationMeta } = action.payload;
|
||||||
|
|
||||||
const viewId = customViewId || -1;
|
const viewId = customViewId || -1;
|
||||||
const view = state.views[viewId] || {};
|
const view = state.views[viewId] || {};
|
||||||
|
|
||||||
state.views[viewId] = {
|
state.views[viewId] = {
|
||||||
...view,
|
...view,
|
||||||
pages: {
|
pages: {
|
||||||
...(state.views?.[viewId]?.pages || {}),
|
...(state.views?.[viewId]?.pages || {}),
|
||||||
[pagination.page]: {
|
[paginationMeta.total]: {
|
||||||
ids: vendors.map((i) => i.id),
|
ids: vendors.map((i) => i.id),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.VENDOR_DELETE]: (state, action) => {
|
[t.VENDOR_DELETE]: (state, action) => {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
|
|
||||||
@@ -58,39 +62,6 @@ const reducer = createReducer(initialState, {
|
|||||||
delete state.items[id];
|
delete state.items[id];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// ...viewPaginationSetReducer(t.VENDORS_PAGINATION_SET),
|
||||||
[t.VENDORS_SET_CURRENT_VIEW]: (state, action) => {
|
...createTableQueryReducers('VENDORS'),
|
||||||
state.currentViewId = action.currentViewId;
|
|
||||||
},
|
|
||||||
|
|
||||||
[t.VENDORS_PAGINATION_SET]: (state, action) => {
|
|
||||||
const { pagination, customViewId } = action.payload;
|
|
||||||
|
|
||||||
const mapped = {
|
|
||||||
pageSize: parseInt(pagination.pageSize, 10),
|
|
||||||
page: parseInt(pagination.page, 10),
|
|
||||||
total: parseInt(pagination.total, 10),
|
|
||||||
};
|
|
||||||
const paginationMeta = {
|
|
||||||
...mapped,
|
|
||||||
pagesCount: Math.ceil(mapped.total / mapped.pageSize),
|
|
||||||
pageIndex: Math.max(mapped.page - 1, 0),
|
|
||||||
};
|
|
||||||
|
|
||||||
state.views = {
|
|
||||||
...state.views,
|
|
||||||
[customViewId]: {
|
|
||||||
...(state.views?.[customViewId] || {}),
|
|
||||||
paginationMeta,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// [t.VENDOR_SET]: (state, action) => {
|
|
||||||
// const { id, vendor } = action.payload;
|
|
||||||
// const _venders = state.items[id] || {};
|
|
||||||
// state.items[id] = { ..._venders, ...vendor };
|
|
||||||
// },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default createTableQueryReducers('vendors', reducer);
|
|
||||||
|
|||||||
26
client/src/store/vendors/vendors.selectors.js
vendored
26
client/src/store/vendors/vendors.selectors.js
vendored
@@ -1,10 +1,18 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
import {
|
||||||
|
pickItemsFromIds,
|
||||||
|
paginationLocationQuery,
|
||||||
|
defaultPaginationMeta,
|
||||||
|
} from 'store/selectors';
|
||||||
|
|
||||||
const vendorsTableQuery = (state) => {
|
const vendorsTableQuery = (state) => {
|
||||||
return state.vendors.tableQuery;
|
return state.vendors.tableQuery;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const vendorByIdSelector = (state, props) => {
|
||||||
|
return state.vendors.items[props.vendorId];
|
||||||
|
};
|
||||||
|
|
||||||
export const getVendorsTableQuery = createSelector(
|
export const getVendorsTableQuery = createSelector(
|
||||||
paginationLocationQuery,
|
paginationLocationQuery,
|
||||||
vendorsTableQuery,
|
vendorsTableQuery,
|
||||||
@@ -18,7 +26,10 @@ export const getVendorsTableQuery = createSelector(
|
|||||||
|
|
||||||
const vendorsPageSelector = (state, props, query) => {
|
const vendorsPageSelector = (state, props, query) => {
|
||||||
const viewId = state.vendors.currentViewId;
|
const viewId = state.vendors.currentViewId;
|
||||||
return state.vendors.views?.[viewId]?.pages?.[query.page];
|
const currentView = state.vendors.views?.[viewId];
|
||||||
|
const currentPageId = currentView?.pages;
|
||||||
|
return currentView?.pages?.[currentPageId];
|
||||||
|
// return state.vendors.views?.[viewId]?.pages?.[query.page];
|
||||||
};
|
};
|
||||||
|
|
||||||
const vendorsItemsSelector = (state) => state.vendors.items;
|
const vendorsItemsSelector = (state) => state.vendors.items;
|
||||||
@@ -41,14 +52,13 @@ const vendorsPaginationSelector = (state, props) => {
|
|||||||
|
|
||||||
export const getVendorsPaginationMetaFactory = () =>
|
export const getVendorsPaginationMetaFactory = () =>
|
||||||
createSelector(vendorsPaginationSelector, (vendorPage) => {
|
createSelector(vendorsPaginationSelector, (vendorPage) => {
|
||||||
return vendorPage?.paginationMeta || {};
|
return {
|
||||||
|
...defaultPaginationMeta(),
|
||||||
|
...(vendorPage?.paginationMeta || {}),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const vendorByIdSelector = (state, props) => {
|
export const getVendorByIdFactory = () =>
|
||||||
return state.vendors.items[props.vendorId];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getEstimateByIdFactory = () =>
|
|
||||||
createSelector(vendorByIdSelector, (vendor) => {
|
createSelector(vendorByIdSelector, (vendor) => {
|
||||||
return vendor;
|
return vendor;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user