mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -83,8 +83,8 @@ export default [
|
||||
href: '/customers',
|
||||
},
|
||||
{
|
||||
text: <T id={'new_customers'} />,
|
||||
href: '/customers/new',
|
||||
text: <T id={'vendors'} />,
|
||||
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,
|
||||
deleteVendor,
|
||||
fetchVendorsTable,
|
||||
fetchVendor,
|
||||
} from 'store/vendors/vendors.actions';
|
||||
import t from 'store/types';
|
||||
|
||||
|
||||
const mapDipatchToProps = (dispatch) => ({
|
||||
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 = {}) =>
|
||||
dispatch(fetchVendorsTable({ query: { ...query } })),
|
||||
requestDeleteVender: (id) => dispatch(deleteVendor({ id })),
|
||||
|
||||
changeVendorView: (id) =>
|
||||
dispatch({
|
||||
type: t.VENDORS_SET_CURRENT_VIEW,
|
||||
@@ -29,4 +29,3 @@ const mapDipatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
@@ -823,4 +823,22 @@ export default {
|
||||
the_item_has_associated_transactions: 'The item has associated transactions.',
|
||||
customer_has_sales_invoices: 'Customer has sales invoices',
|
||||
account_name_is_already_used: 'Account name is already used.',
|
||||
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',
|
||||
},
|
||||
|
||||
// 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
|
||||
{
|
||||
path: `/estimates/:id/edit`,
|
||||
@@ -280,14 +303,16 @@ export default [
|
||||
{
|
||||
path: `/payment-receive/:id/edit`,
|
||||
component: LazyLoader({
|
||||
loader: () => import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
||||
loader: () =>
|
||||
import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
||||
}),
|
||||
breadcrumb: 'Edit',
|
||||
},
|
||||
{
|
||||
path: `/payment-receive/new`,
|
||||
component: LazyLoader({
|
||||
loader: () => import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
||||
loader: () =>
|
||||
import('containers/Sales/PaymentReceive/PaymentReceiveFormPage'),
|
||||
}),
|
||||
breadcrumb: 'New Payment Receive',
|
||||
},
|
||||
@@ -355,8 +380,7 @@ export default [
|
||||
{
|
||||
path: `/payment-mades`,
|
||||
component: LazyLoader({
|
||||
loader: () =>
|
||||
import('containers/Purchases/PaymentMades/PaymentMadeList'),
|
||||
loader: () => import('containers/Purchases/PaymentMades/PaymentMadeList'),
|
||||
}),
|
||||
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,
|
||||
payload: { loading: true },
|
||||
});
|
||||
|
||||
ApiService.get(`vendors`, { params: { ...pageQuery, ...query } })
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.VENDORS_PAGE_SET,
|
||||
payload: {
|
||||
vendors: response.data.vendors,
|
||||
pagination: response.data.pagination,
|
||||
customViewId: response.data.customViewId || -1,
|
||||
paginationMeta: response.data.pagination,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
@@ -37,7 +36,6 @@ export const fetchVendorsTable = ({ query }) => {
|
||||
type: t.VENDORS_TABLE_LOADING,
|
||||
payload: { loading: false },
|
||||
});
|
||||
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -81,18 +79,32 @@ export const submitVendor = ({ form }) => {
|
||||
new Promise((resolve, reject) => {
|
||||
ApiService.post('vendors', form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
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 { createTableQueryReducers } from 'store/queryReducers';
|
||||
import {
|
||||
viewPaginationSetReducer,
|
||||
createTableQueryReducers,
|
||||
} from 'store/journalNumber.reducer';
|
||||
|
||||
import t from 'store/types';
|
||||
|
||||
@@ -7,19 +10,23 @@ const initialState = {
|
||||
items: {},
|
||||
views: {},
|
||||
loading: false,
|
||||
currentViewId: -1,
|
||||
|
||||
tableQuery: {
|
||||
page_size: 5,
|
||||
page: 1,
|
||||
},
|
||||
currentViewId: -1,
|
||||
};
|
||||
|
||||
const reducer = createReducer(initialState, {
|
||||
export default 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) => {
|
||||
const { loading } = action.payload;
|
||||
state.loading = loading;
|
||||
},
|
||||
|
||||
[t.VENDORS_ITEMS_SET]: (state, action) => {
|
||||
const { vendors } = action.payload;
|
||||
const _vendors = {};
|
||||
@@ -33,24 +40,21 @@ const reducer = createReducer(initialState, {
|
||||
..._vendors,
|
||||
};
|
||||
},
|
||||
|
||||
[t.VENDORS_PAGE_SET]: (state, action) => {
|
||||
const { customViewId, vendors, pagination } = action.payload;
|
||||
const { customViewId, vendors, paginationMeta } = action.payload;
|
||||
|
||||
const viewId = customViewId || -1;
|
||||
const view = state.views[viewId] || {};
|
||||
|
||||
state.views[viewId] = {
|
||||
...view,
|
||||
pages: {
|
||||
...(state.views?.[viewId]?.pages || {}),
|
||||
[pagination.page]: {
|
||||
[paginationMeta.total]: {
|
||||
ids: vendors.map((i) => i.id),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
[t.VENDOR_DELETE]: (state, action) => {
|
||||
const { id } = action.payload;
|
||||
|
||||
@@ -58,39 +62,6 @@ const reducer = createReducer(initialState, {
|
||||
delete state.items[id];
|
||||
}
|
||||
},
|
||||
|
||||
[t.VENDORS_SET_CURRENT_VIEW]: (state, action) => {
|
||||
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 };
|
||||
// },
|
||||
// ...viewPaginationSetReducer(t.VENDORS_PAGINATION_SET),
|
||||
...createTableQueryReducers('VENDORS'),
|
||||
});
|
||||
|
||||
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 { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
||||
import {
|
||||
pickItemsFromIds,
|
||||
paginationLocationQuery,
|
||||
defaultPaginationMeta,
|
||||
} from 'store/selectors';
|
||||
|
||||
const vendorsTableQuery = (state) => {
|
||||
return state.vendors.tableQuery;
|
||||
};
|
||||
|
||||
const vendorByIdSelector = (state, props) => {
|
||||
return state.vendors.items[props.vendorId];
|
||||
};
|
||||
|
||||
export const getVendorsTableQuery = createSelector(
|
||||
paginationLocationQuery,
|
||||
vendorsTableQuery,
|
||||
@@ -18,7 +26,10 @@ export const getVendorsTableQuery = createSelector(
|
||||
|
||||
const vendorsPageSelector = (state, props, query) => {
|
||||
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;
|
||||
@@ -41,14 +52,13 @@ const vendorsPaginationSelector = (state, props) => {
|
||||
|
||||
export const getVendorsPaginationMetaFactory = () =>
|
||||
createSelector(vendorsPaginationSelector, (vendorPage) => {
|
||||
return vendorPage?.paginationMeta || {};
|
||||
return {
|
||||
...defaultPaginationMeta(),
|
||||
...(vendorPage?.paginationMeta || {}),
|
||||
};
|
||||
});
|
||||
|
||||
const vendorByIdSelector = (state, props) => {
|
||||
return state.vendors.items[props.vendorId];
|
||||
};
|
||||
|
||||
export const getEstimateByIdFactory = () =>
|
||||
export const getVendorByIdFactory = () =>
|
||||
createSelector(vendorByIdSelector, (vendor) => {
|
||||
return vendor;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user