This commit is contained in:
elforjani3
2021-02-25 18:31:34 +02:00
84 changed files with 1018 additions and 681 deletions

View File

@@ -51,6 +51,6 @@ function Dashboard({
<DrawersContainer /> <DrawersContainer />
</DashboardLoadingIndicator> </DashboardLoadingIndicator>
); );
} }
export default compose(withSettingsActions)(Dashboard); export default compose(withSettingsActions)(Dashboard);

View File

@@ -1,7 +1,6 @@
import React, { useEffect, Suspense } from 'react'; import React from 'react';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import routes from 'routes/dashboard'; import routes from 'routes/dashboard';
import DashboardPage from './DashboardPage'; import DashboardPage from './DashboardPage';
/** /**
@@ -18,6 +17,7 @@ export default function DashboardContentRoute() {
path={`${route.path}`} path={`${route.path}`}
> >
<DashboardPage <DashboardPage
name={route.name}
Component={route.component} Component={route.component}
pageTitle={route.pageTitle} pageTitle={route.pageTitle}
backLink={route.backLink} backLink={route.backLink}

View File

@@ -6,14 +6,14 @@ import { For } from 'components';
function FooterLinkItem({ title, link }) { function FooterLinkItem({ title, link }) {
return ( return (
<div class=""> <div class="">
<a href={link} target="_blank"> <a href={link} target="_blank" rel="noopener noreferrer">
{title} {title}
</a> </a>
</div> </div>
); );
} }
export default function DashboardFooter({}) { export default function DashboardFooter() {
return ( return (
<div class="dashboard__footer"> <div class="dashboard__footer">
<div class="footer-links"> <div class="footer-links">

View File

@@ -11,6 +11,7 @@ function DashboardPage({
backLink, backLink,
sidebarShrink, sidebarShrink,
Component, Component,
name,
// #withDashboardActions // #withDashboardActions
changePageTitle, changePageTitle,
@@ -44,6 +45,15 @@ function DashboardPage({
}; };
}, [resetSidebarPreviousExpand, sidebarShrink, setSidebarShrink]); }, [resetSidebarPreviousExpand, sidebarShrink, setSidebarShrink]);
useEffect(() => {
const className = `page-${name}`;
name && document.body.classList.add(className);
return () => {
name && document.body.classList.remove(className);
};
}, [name]);
return ( return (
<div className={CLASSES.DASHBOARD_PAGE}> <div className={CLASSES.DASHBOARD_PAGE}>
<Suspense fallback={''}> <Suspense fallback={''}>

View File

@@ -40,6 +40,7 @@ export default function ItemsListCell({
purchasable={filterPurchasable} purchasable={filterPurchasable}
inputProps={{ inputProps={{
inputRef: (ref) => (fieldRef.current = ref), inputRef: (ref) => (fieldRef.current = ref),
placeholder: 'Enter an item...'
}} }}
openOnKeyDown={true} openOnKeyDown={true}
blurOnSelectClose={false} blurOnSelectClose={false}

View File

@@ -29,6 +29,7 @@ const PercentFieldCell = ({
return ( return (
<FormGroup intent={error ? Intent.DANGER : null}> <FormGroup intent={error ? Intent.DANGER : null}>
<MoneyInputGroup <MoneyInputGroup
prefix={'%'}
value={value} value={value}
onChange={handleChange} onChange={handleChange}
onBlurValue={handleBlurChange} onBlurValue={handleBlurChange}

View File

@@ -1,22 +1,50 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { If } from 'components'; import { If } from 'components';
import { ConditionalWrapper } from 'utils'; import { Skeleton } from 'components';
import TableContext from './TableContext'; import TableContext from './TableContext';
import { isCellLoading } from './utils';
/** /**
* Tabl cell. * Table cell.
*/ */
export default function TableCell({ export default function TableCell({
cell, cell,
row: { depth, getToggleRowExpandedProps, isExpanded }, row: { index: rowIndex, depth, getToggleRowExpandedProps, isExpanded },
index, index,
}) { }) {
const { const {
props: { expandToggleColumn, expandColumnSpace, expandable }, props: {
expandToggleColumn,
expandColumnSpace,
expandable,
cellsLoading,
cellsLoadingCoords,
},
} = useContext(TableContext); } = useContext(TableContext);
const isExpandColumn = expandToggleColumn === index; const isExpandColumn = expandToggleColumn === index;
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = {};
// Detarmines whether the current cell is loading.
const cellLoading = isCellLoading(
cellsLoading,
cellsLoadingCoords,
rowIndex,
cell.column.id,
);
if (cellLoading) {
return (
<div
{...cell.getCellProps({
className: classNames(cell.column.className, 'td'),
})}
>
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
</div>
);
}
return ( return (
<div <div
@@ -27,9 +55,12 @@ export default function TableCell({
})} })}
> >
<div <div
className={classNames({ className={classNames(
'text-overview': cell.column.textOverview, {
}, 'cell-inner')} 'text-overview': cell.column.textOverview,
},
'cell-inner',
)}
style={{ style={{
'padding-left': 'padding-left':
isExpandColumn && expandable isExpandColumn && expandable

View File

@@ -6,9 +6,13 @@ import TableContext from './TableContext';
*/ */
export default function TableFooter() { export default function TableFooter() {
const { const {
props: { footer },
table: { footerGroups }, table: { footerGroups },
} = useContext(TableContext); } = useContext(TableContext);
// Can't contiunue if the footer is disabled.
if (!footer) { return null; }
return ( return (
<div class="tfooter"> <div class="tfooter">
{footerGroups.map((group) => ( {footerGroups.map((group) => (

View File

@@ -0,0 +1,10 @@
export const isCellLoading = (loading, cellsCoords, rowIndex, columnId) => {
if (!loading) {
return false;
}
return !cellsCoords
? true
: cellsCoords.some(
(cellCoord) => cellCoord[0] === rowIndex && cellCoord[1] === columnId,
);
};

View File

@@ -0,0 +1,41 @@
import React, { useState } from 'react';
import { Collapse } from '@blueprintjs/core';
import classNames from 'classnames';
/**
* Postbox.
*/
export default function Postbox({
defaultOpen = true,
toggable = true,
title,
children,
}) {
const [isOpen, setIsOpen] = useState(defaultOpen);
// Handle the title click.
const handleTitleClick = () => {
if (toggable) {
setIsOpen(!isOpen);
}
};
return (
<div
class={classNames('postbox', {
'is-toggable': toggable,
})}
>
<div class="postbox__header" onClick={handleTitleClick}>
<h5 class="postbox__title">{title}</h5>
<span class="postbox__toggle-indicator"></span>
</div>
<div class="postbox__content">
<Collapse isOpen={isOpen}>
<div class="postbox__content-inner">{children}</div>
</Collapse>
</div>
</div>
);
}

View File

@@ -51,6 +51,7 @@ import DashboardPageContent from './Dashboard/DashboardPageContent';
import DashboardInsider from './Dashboard/DashboardInsider'; import DashboardInsider from './Dashboard/DashboardInsider';
import Drawer from './Drawer/Drawer'; import Drawer from './Drawer/Drawer';
import DrawerSuspense from './Drawer/DrawerSuspense'; import DrawerSuspense from './Drawer/DrawerSuspense';
import Postbox from './Postbox';
const Hint = FieldHint; const Hint = FieldHint;
@@ -109,4 +110,5 @@ export {
DashboardInsider, DashboardInsider,
Drawer, Drawer,
DrawerSuspense, DrawerSuspense,
Postbox
}; };

View File

@@ -5,7 +5,8 @@ export default [
{ {
text: <T id={'homepage'} />, text: <T id={'homepage'} />,
disabled: false, disabled: false,
href: '/homepage', href: '/',
matchExact: true,
}, },
{ {
text: 'Sales & inventory', text: 'Sales & inventory',

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import 'style/pages/ManualJournal/List.scss';
import { DashboardContentTable, DashboardPageContent } from 'components'; import { DashboardContentTable, DashboardPageContent } from 'components';
import { ManualJournalsListProvider } from './ManualJournalsListProvider'; import { ManualJournalsListProvider } from './ManualJournalsListProvider';
@@ -9,11 +11,8 @@ import ManualJournalsDataTable from './ManualJournalsDataTable';
import ManualJournalsActionsBar from './ManualJournalActionsBar'; import ManualJournalsActionsBar from './ManualJournalActionsBar';
import withManualJournals from './withManualJournals'; import withManualJournals from './withManualJournals';
import { transformTableStateToQuery, compose } from 'utils'; import { transformTableStateToQuery, compose } from 'utils';
import 'style/pages/ManualJournal/List.scss';
/** /**
* Manual journals table. * Manual journals table.
*/ */

View File

@@ -169,6 +169,7 @@ function MakeJournalEntriesForm({
<MakeJournalFormFooter /> <MakeJournalFormFooter />
<MakeJournalFormFloatingActions /> <MakeJournalFormFloatingActions />
{/* --------- Dialogs --------- */}
<MakeJournalFormDialogs /> <MakeJournalFormDialogs />
</Form> </Form>
</Formik> </Formik>

View File

@@ -1,12 +1,27 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import MakeJournalEntriesHeaderFields from "./MakeJournalEntriesHeaderFields"; import MakeJournalEntriesHeaderFields from "./MakeJournalEntriesHeaderFields";
import { PageFormBigNumber } from 'components';
import { safeSumBy } from 'utils';
export default function MakeJournalEntriesHeader() { export default function MakeJournalEntriesHeader() {
const { values: { entries } } = useFormikContext();
const totalCredit = safeSumBy(entries, 'credit');
const totalDebit = safeSumBy(entries, 'debit');
const total = Math.max(totalCredit, totalDebit);
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<MakeJournalEntriesHeaderFields /> <MakeJournalEntriesHeaderFields />
<PageFormBigNumber
label={'Due Amount'}
amount={total}
currencyCode={'USD'}
/>
</div> </div>
) )
} }

View File

@@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import 'style/pages/ManualJournal/MakeJournal.scss';
import MakeJournalEntriesForm from './MakeJournalEntriesForm'; import MakeJournalEntriesForm from './MakeJournalEntriesForm';
import { MakeJournalProvider } from './MakeJournalProvider'; import { MakeJournalProvider } from './MakeJournalProvider';
import 'style/pages/ManualJournal/MakeJournal.scss';
/** /**
* Make journal entries page. * Make journal entries page.
*/ */

View File

@@ -9,9 +9,7 @@ import { updateDataReducer } from './utils';
import { useMakeJournalFormContext } from './MakeJournalProvider'; import { useMakeJournalFormContext } from './MakeJournalProvider';
import { useJournalTableEntriesColumns } from './components'; import { useJournalTableEntriesColumns } from './components';
import JournalDeleteEntriesAlert from 'containers/Alerts/ManualJournals/JournalDeleteEntriesAlert';
import { compose } from 'redux'; import { compose } from 'redux';
import { repeatValue } from 'utils';
/** /**
* Make journal entries table component. * Make journal entries table component.
@@ -32,13 +30,7 @@ function MakeJournalEntriesTable({
// Memorized data table columns. // Memorized data table columns.
const columns = useJournalTableEntriesColumns(); const columns = useJournalTableEntriesColumns();
// Handles click new line.
const onClickNewRow = () => {
const newRows = [...entries, defaultEntry];
saveInvoke(onChange, newRows);
};
// Handles update datatable data. // Handles update datatable data.
const handleUpdateData = (rowIndex, columnId, value) => { const handleUpdateData = (rowIndex, columnId, value) => {
const newRows = updateDataReducer(entries, rowIndex, columnId, value); const newRows = updateDataReducer(entries, rowIndex, columnId, value);
@@ -50,61 +42,28 @@ function MakeJournalEntriesTable({
const newRows = removeRowsByIndex(entries, rowIndex); const newRows = removeRowsByIndex(entries, rowIndex);
saveInvoke(onChange, newRows); saveInvoke(onChange, newRows);
}; };
// Handle clear all lines action.
const handleClickClearAllLines = () => {
openAlert('make-journal-delete-all-entries');
};
// Handle clear all lines alaert confirm.
const handleCofirmClearEntriesAlert = () => {
const newRows = repeatValue(defaultEntry, initialLinesNumber);
saveInvoke(onChange, newRows);
};
return ( return (
<>
<DataTableEditable
columns={columns}
data={entries}
sticky={true}
totalRow={true}
payload={{
accounts,
errors: error,
updateData: handleUpdateData,
removeRow: handleRemoveRow,
contacts: customers.map((customer) => ({
...customer,
contact_type: 'customer',
})),
autoFocus: ['account_id', 0],
}}
actions={
<>
<Button
small={true}
className={'button--secondary button--new-line'}
onClick={onClickNewRow}
>
<T id={'new_lines'} />
</Button>
<Button <DataTableEditable
small={true} columns={columns}
className={'button--secondary button--clear-lines ml1'} data={entries}
onClick={handleClickClearAllLines} sticky={true}
> totalRow={true}
<T id={'clear_all_lines'} /> footer={true}
</Button> payload={{
</> accounts,
} errors: error,
/> updateData: handleUpdateData,
<JournalDeleteEntriesAlert removeRow: handleRemoveRow,
name={'make-journal-delete-all-entries'} contacts: customers.map((customer) => ({
onConfirm={handleCofirmClearEntriesAlert} ...customer,
/> contact_type: 'customer',
</> })),
autoFocus: ['account_id', 0],
}}
/>
); );
} }

View File

@@ -4,39 +4,41 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { FormGroup, TextArea } from '@blueprintjs/core'; import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { ErrorMessage, Row, Col } from 'components'; import { Postbox, ErrorMessage, Row, Col } from 'components';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import { inputIntent } from 'utils'; import { inputIntent } from 'utils';
export default function MakeJournalFormFooter() { export default function MakeJournalFormFooter() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Journal details'} defaultOpen={false}>
<Col md={8}> <Row>
<FastField name={'description'}> <Col md={8}>
{({ field, meta: { error, touched } }) => ( <FastField name={'description'}>
<FormGroup {({ field, meta: { error, touched } }) => (
label={<T id={'description'} />} <FormGroup
className={'form-group--description'} label={<T id={'description'} />}
intent={inputIntent({ error, touched })} className={'form-group--description'}
helperText={<ErrorMessage name="description" />} intent={inputIntent({ error, touched })}
fill={true} helperText={<ErrorMessage name="description" />}
> fill={true}
<TextArea fill={true} {...field} /> >
</FormGroup> <TextArea fill={true} {...field} />
)} </FormGroup>
</FastField> )}
</Col> </FastField>
</Col>
<Col md={4}> <Col md={4}>
<Dragzone <Dragzone
initialFiles={[]} initialFiles={[]}
// onDrop={handleDropFiles} // onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile} // onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -29,7 +29,9 @@ function InvoiceNumberDialogContent({
const { mutateAsync: saveSettings } = useSaveSettings(); const { mutateAsync: saveSettings } = useSaveSettings();
const handleSubmitForm = (values, { setSubmitting }) => { const handleSubmitForm = (values, { setSubmitting }) => {
const options = optionsMapToArray(values).map((option) => ({ const { mode, ...autoModeValues } = values;
const options = optionsMapToArray(autoModeValues).map((option) => ({
key: option.key, key: option.key,
...option, ...option,
group: 'sales_invoices', group: 'sales_invoices',

View File

@@ -14,7 +14,6 @@ function InvoiceNumberDialog({
isOpen, isOpen,
onConfirm, onConfirm,
}) { }) {
return ( return (
<Dialog <Dialog
title={<T id={'invoice_number_settings'} />} title={<T id={'invoice_number_settings'} />}

View File

@@ -1,7 +1,8 @@
import React, { useCallback } from 'react'; import React, { useEffect, useCallback } from 'react';
import { Button } from '@blueprintjs/core'; import { Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { useItem } from 'hooks/query';
import ItemsEntriesDeleteAlert from 'containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert'; import ItemsEntriesDeleteAlert from 'containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert';
import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertActions from 'containers/Alert/withAlertActions';
@@ -17,7 +18,15 @@ import {
removeRowsByIndex, removeRowsByIndex,
compose, compose,
} from 'utils'; } from 'utils';
import { updateItemsEntriesTotal } from './utils'; import { updateItemsEntriesTotal, ITEM_TYPE } from './utils';
import { last } from 'lodash';
const updateAutoAddNewLine = (defaultEntry) => (entries) => {
const newEntries = [...entries];
const lastEntry = last(newEntries);
return (lastEntry.item_id) ? [...entries, defaultEntry] : [...entries];
};
/** /**
* Items entries table. * Items entries table.
@@ -34,11 +43,64 @@ function ItemsEntriesTable({
errors, errors,
onUpdateData, onUpdateData,
linesNumber, linesNumber,
itemType, // sellable or purchasable
}) { }) {
const [rows, setRows] = React.useState(initialEntries); const [rows, setRows] = React.useState(initialEntries);
const [rowItem, setRowItem] = React.useState(null);
const [cellsLoading, setCellsLoading] = React.useState(null);
// Fetches the item details.
const { data: item, isFetching: isItemFetching } = useItem(
rowItem && rowItem.itemId,
{
enabled: !!rowItem,
},
);
// Once the item start loading give the table cells loading state.
useEffect(() => {
if (rowItem && isItemFetching) {
setCellsLoading([
[rowItem.rowIndex, 'rate'],
[rowItem.rowIndex, 'description'],
[rowItem.rowIndex, 'quantity'],
[rowItem.rowIndex, 'discount'],
]);
} else {
setCellsLoading(null);
}
}, [isItemFetching, setCellsLoading, rowItem]);
// Once the item selected and fetched set the initial details to the table.
useEffect(() => {
if (item && rowItem) {
const { rowIndex } = rowItem;
const price =
itemType === ITEM_TYPE.PURCHASABLE
? item.purchase_price
: item.sell_price;
const description =
itemType === ITEM_TYPE.PURCHASABLE
? item.purchase_description
: item.sell_description;
// Update the rate, description and quantity data of the row.
const newRows = compose(
updateItemsEntriesTotal,
updateTableRow(rowIndex, 'rate', price),
updateTableRow(rowIndex, 'description', description),
updateTableRow(rowIndex, 'quantity', 1),
)(rows);
setRows(newRows);
setRowItem(null);
saveInvoke(onUpdateData, newRows);
}
}, [item, rowItem, rows, itemType, onUpdateData]);
// Allows to observes `entries` to make table rows outside controlled. // Allows to observes `entries` to make table rows outside controlled.
React.useEffect(() => { useEffect(() => {
if (entries && entries !== rows) { if (entries && entries !== rows) {
setRows(entries); setRows(entries);
} }
@@ -50,15 +112,19 @@ function ItemsEntriesTable({
// Handles the editor data update. // Handles the editor data update.
const handleUpdateData = useCallback( const handleUpdateData = useCallback(
(rowIndex, columnId, value) => { (rowIndex, columnId, value) => {
if (columnId === 'item_id') {
setRowItem({ rowIndex, columnId, itemId: value });
}
const newRows = compose( const newRows = compose(
updateAutoAddNewLine(defaultEntry),
updateItemsEntriesTotal, updateItemsEntriesTotal,
updateTableRow(rowIndex, columnId, value), updateTableRow(rowIndex, columnId, value),
)(entries); )(rows);
setRows(newRows); setRows(newRows);
onUpdateData(newRows); onUpdateData(newRows);
}, },
[entries, onUpdateData], [rows, defaultEntry, onUpdateData],
); );
// Handle table rows removing by index. // Handle table rows removing by index.
@@ -80,9 +146,7 @@ function ItemsEntriesTable({
openAlert('items-entries-clear-lines'); openAlert('items-entries-clear-lines');
}; };
/** // Handle alert confirm of clear all lines.
* Handle alert confirm of clear all lines.
*/
const handleClearLinesAlertConfirm = () => { const handleClearLinesAlertConfirm = () => {
const newRows = repeatValue(defaultEntry, linesNumber); const newRows = repeatValue(defaultEntry, linesNumber);
setRows(newRows); setRows(newRows);
@@ -94,8 +158,12 @@ function ItemsEntriesTable({
<DataTableEditable <DataTableEditable
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)} className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
columns={columns} columns={columns}
data={entries} data={rows}
sticky={true} sticky={true}
progressBarLoading={isItemFetching}
cellsLoading={isItemFetching}
cellsLoadingCoords={cellsLoading}
footer={true}
payload={{ payload={{
items, items,
errors: errors || [], errors: errors || [],
@@ -103,25 +171,7 @@ function ItemsEntriesTable({
removeRow: handleRemoveRow, removeRow: handleRemoveRow,
autoFocus: ['item_id', 0], autoFocus: ['item_id', 0],
}} }}
actions={
<>
<Button
small={true}
className={'button--secondary button--new-line'}
onClick={onClickNewRow}
>
<T id={'new_lines'} />
</Button>
<Button
small={true}
className={'button--secondary button--clear-lines ml1'}
onClick={handleClickClearAllLines}
>
<T id={'clear_all_lines'} />
</Button>
</>
}
/> />
<ItemsEntriesDeleteAlert <ItemsEntriesDeleteAlert
name={'items-entries-clear-lines'} name={'items-entries-clear-lines'}

View File

@@ -1,9 +1,8 @@
import React from 'react'; import React from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { Tooltip, Button, Intent, Position } from '@blueprintjs/core'; import { Tooltip, Button, Intent, Position } from '@blueprintjs/core';
import { sumBy } from 'lodash';
import { Hint, Icon } from 'components'; import { Hint, Icon } from 'components';
import { formattedAmount } from 'utils'; import { formattedAmount, safeSumBy } from 'utils';
import { import {
InputGroupCell, InputGroupCell,
MoneyFieldCell, MoneyFieldCell,
@@ -62,7 +61,7 @@ export function ActionsCellRenderer({
* Quantity total footer cell. * Quantity total footer cell.
*/ */
export function QuantityTotalFooterCell({ rows }) { export function QuantityTotalFooterCell({ rows }) {
const quantity = sumBy(rows, r => parseInt(r.original.quantity, 10)); const quantity = safeSumBy(rows, 'original.quantity');
return <span>{ quantity }</span>; return <span>{ quantity }</span>;
} }
@@ -70,7 +69,7 @@ export function QuantityTotalFooterCell({ rows }) {
* Total footer cell. * Total footer cell.
*/ */
export function TotalFooterCell({ rows }) { export function TotalFooterCell({ rows }) {
const total = sumBy(rows, 'original.total'); const total = safeSumBy(rows, 'original.total');
return <span>{ formattedAmount(total, 'USD') }</span>; return <span>{ formattedAmount(total, 'USD') }</span>;
} }
@@ -110,9 +109,8 @@ export function useEditableItemsEntriesColumns() {
Cell: ItemsListCell, Cell: ItemsListCell,
Footer: ItemFooterCell, Footer: ItemFooterCell,
disableSortBy: true, disableSortBy: true,
width: 180, width: 130,
// filterPurchasable: filterPurchasableItems, className: 'item',
// filterSellable: filterSellableItems,
}, },
{ {
Header: formatMessage({ id: 'description' }), Header: formatMessage({ id: 'description' }),
@@ -120,7 +118,7 @@ export function useEditableItemsEntriesColumns() {
Cell: InputGroupCell, Cell: InputGroupCell,
disableSortBy: true, disableSortBy: true,
className: 'description', className: 'description',
width: 100, width: 120,
}, },
{ {
Header: formatMessage({ id: 'quantity' }), Header: formatMessage({ id: 'quantity' }),
@@ -128,7 +126,7 @@ export function useEditableItemsEntriesColumns() {
Cell: NumericInputCell, Cell: NumericInputCell,
Footer: QuantityTotalFooterCell, Footer: QuantityTotalFooterCell,
disableSortBy: true, disableSortBy: true,
width: 80, width: 70,
className: 'quantity', className: 'quantity',
}, },
{ {
@@ -136,7 +134,7 @@ export function useEditableItemsEntriesColumns() {
accessor: 'rate', accessor: 'rate',
Cell: MoneyFieldCell, Cell: MoneyFieldCell,
disableSortBy: true, disableSortBy: true,
width: 80, width: 70,
className: 'rate', className: 'rate',
}, },
{ {
@@ -144,7 +142,7 @@ export function useEditableItemsEntriesColumns() {
accessor: 'discount', accessor: 'discount',
Cell: PercentFieldCell, Cell: PercentFieldCell,
disableSortBy: true, disableSortBy: true,
width: 80, width: 60,
className: 'discount', className: 'discount',
}, },
{ {
@@ -153,7 +151,7 @@ export function useEditableItemsEntriesColumns() {
accessor: 'total', accessor: 'total',
Cell: TotalCell, Cell: TotalCell,
disableSortBy: true, disableSortBy: true,
width: 120, width: 100,
className: 'total', className: 'total',
}, },
{ {

View File

@@ -23,4 +23,9 @@ export function updateItemsEntriesTotal(rows) {
...row, ...row,
total: calcItemEntryTotal(row.discount, row.quantity, row.rate) total: calcItemEntryTotal(row.discount, row.quantity, row.rate)
})); }));
}; };
export const ITEM_TYPE = {
SELLABLE: 'SELLABLE',
PURCHASABLE: 'PURCHASABLE',
};

View File

@@ -118,8 +118,8 @@ function ExpenseForm({
}; };
// Handle request error // Handle request error
const handleError = (error) => { const handleError = ({ response: { data: { errors } } }) => {
transformErrors(error, { setErrors }); transformErrors(errors, { setErrors });
setSubmitting(false); setSubmitting(false);
}; };
if (isNewMode) { if (isNewMode) {

View File

@@ -15,7 +15,7 @@ import { transformUpdatedRows, compose, saveInvoke, repeatValue } from 'utils';
* Expenses form entries. * Expenses form entries.
*/ */
function ExpenseFormEntriesTable({ function ExpenseFormEntriesTable({
// #withAlertActions // #withAlertActions
openAlert, openAlert,
// #ownPorps // #ownPorps
@@ -57,65 +57,21 @@ function ExpenseFormEntriesTable({
[entries, onChange], [entries, onChange],
); );
// Invoke when click on add new line button.
const onClickNewRow = () => {
const newRows = [...entries, defaultEntry];
saveInvoke(onChange, newRows);
};
// Invoke when click on clear all lines button.
const handleClickClearAllLines = () => {
openAlert('expense-delete-entries');
};
// handle confirm clear all entries alert.
const handleConfirmClearEntriesAlert = () => {
const newRows = repeatValue(defaultEntry, 3);
saveInvoke(onChange, newRows);
};
return ( return (
<> <DataTableEditable
<DataTableEditable columns={columns}
columns={columns} data={entries}
data={entries} sticky={true}
sticky={true} payload={{
payload={{ accounts: accounts,
accounts: accounts, errors: error,
errors: error, updateData: handleUpdateData,
updateData: handleUpdateData, removeRow: handleRemoveRow,
removeRow: handleRemoveRow, autoFocus: ['expense_account_id', 0],
autoFocus: ['expense_account_id', 0], }}
}} footer={true}
actions={ />
<>
<Button
small={true}
className={'button--secondary button--new-line'}
onClick={onClickNewRow}
>
<T id={'new_lines'} />
</Button>
<Button
small={true}
className={'button--secondary button--clear-lines ml1'}
onClick={handleClickClearAllLines}
>
<T id={'clear_all_lines'} />
</Button>
</>
}
totalRow={true}
/>
<ExpenseDeleteEntriesAlert
name={'expense-delete-entries'}
onConfirm={handleConfirmClearEntriesAlert}
/>
</>
); );
} }
export default compose( export default compose(withAlertActions)(ExpenseFormEntriesTable);
withAlertActions
)(ExpenseFormEntriesTable);

View File

@@ -4,36 +4,38 @@ import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { inputIntent } from 'utils'; import { inputIntent } from 'utils';
import { Row, Dragzone, Col } from 'components'; import { Row, Dragzone, Col, Postbox } from 'components';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
export default function ExpenseFormFooter() { export default function ExpenseFormFooter() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Expense details'} defaultOpen={false}>
<Col md={8}> <Row>
<FastField name={'description'}> <Col md={8}>
{({ field, meta: { error, touched } }) => ( <FastField name={'description'}>
<FormGroup {({ field, meta: { error, touched } }) => (
label={<T id={'description'} />} <FormGroup
className={'form-group--description'} label={<T id={'description'} />}
intent={inputIntent({ error, touched })} className={'form-group--description'}
> intent={inputIntent({ error, touched })}
<TextArea growVertically={true} {...field} /> >
</FormGroup> <TextArea growVertically={true} {...field} />
)} </FormGroup>
</FastField> )}
</Col> </FastField>
</Col>
<Col md={4}> <Col md={4}>
<Dragzone <Dragzone
initialFiles={[]} initialFiles={[]}
// onDrop={handleDropFiles} // onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile} // onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core'; import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { import {
momentFormatter, momentFormatter,
@@ -10,16 +11,13 @@ import {
inputIntent, inputIntent,
handleDateChange, handleDateChange,
} from 'utils'; } from 'utils';
import classNames from 'classnames';
import { import {
CurrencySelectList, CurrencySelectList,
ContactSelecetList, ContactSelecetList,
ErrorMessage,
AccountsSelectList, AccountsSelectList,
FieldRequiredHint, FieldRequiredHint,
Hint, Hint,
} from 'components'; } from 'components';
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes'; import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
import { useExpenseFormContext } from './ExpenseFormPageProvider'; import { useExpenseFormContext } from './ExpenseFormPageProvider';

View File

@@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import 'style/pages/Expense/PageForm.scss';
import ExpenseForm from './ExpenseForm'; import ExpenseForm from './ExpenseForm';
import { ExpenseFormPageProvider } from './ExpenseFormPageProvider'; import { ExpenseFormPageProvider } from './ExpenseFormPageProvider';
import 'style/pages/Expense/PageForm.scss';
/** /**
* Expense page form. * Expense page form.
*/ */

View File

@@ -39,6 +39,8 @@ const defaultInitialValues = {
category_id: '', category_id: '',
sellable: 1, sellable: 1,
purchasable: true, purchasable: true,
sell_description: '',
purchase_description: '',
}; };
/** /**

View File

@@ -1,15 +1,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useFormik } from 'formik'; import { Formik, Form } from 'formik';
import { Row, Col, ErrorMessage } from 'components';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { import { Button, Classes } from '@blueprintjs/core';
Button, import { Intent } from '@blueprintjs/core';
Classes, import { saveInvoke } from 'utils';
FormGroup, import ReferenceNumberFormContent from './ReferenceNumberFormContent';
InputGroup,
Intent,
} from '@blueprintjs/core';
/** /**
* Reference number form. * Reference number form.
@@ -21,6 +17,7 @@ export default function ReferenceNumberForm({
initialNumber, initialNumber,
}) { }) {
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
// mode: Yup.string(),
number_prefix: Yup.string(), number_prefix: Yup.string(),
next_number: Yup.number(), next_number: Yup.number(),
}); });
@@ -33,85 +30,44 @@ export default function ReferenceNumberForm({
[initialPrefix, initialNumber], [initialPrefix, initialNumber],
); );
const { const handleSubmit = (values) => {
errors, debugger;
touched, saveInvoke(onSubmit, values);
handleSubmit, };
isSubmitting,
getFieldProps,
} = useFormik({
enableReinitialize: true,
initialValues: {
...initialValues,
},
validationSchema,
onSubmit: (values, { setSubmitting, setErrors }) => {
onSubmit(values, { setSubmitting, setErrors });
},
});
return (
<div>
<form onSubmit={handleSubmit}>
<div className={Classes.DIALOG_BODY}>
<p className="paragraph">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce
tincidunt porta quam,
</p>
<Row>
{/* ------------- Prefix ------------- */}
<Col xs={6}>
<FormGroup
label={<T id={'prefix'} />}
className={'form-group--'}
intent={errors.number_prefix && touched.number_prefix && Intent.DANGER}
helperText={
<ErrorMessage name={'prefix'} {...{ errors, touched }} />
}
>
<InputGroup
intent={errors.number_prefix && touched.number_prefix && Intent.DANGER}
{...getFieldProps('number_prefix')}
/>
</FormGroup>
</Col>
{/* ------------- Next number ------------- */} return (
<Col xs={6}> <Formik
<FormGroup initialValues={initialValues}
label={<T id={'next_number'} />} validationSchema={validationSchema}
className={'form-group--'} onSubmit={handleSubmit}
intent={ >
errors.next_number && touched.next_number && Intent.DANGER {({ isSubmitting }) => (
} <Form>
helperText={ <div className={Classes.DIALOG_BODY}>
<ErrorMessage name={'next_number'} {...{ errors, touched }} /> <p className="paragraph">
} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce
> tincidunt porta quam,
<InputGroup </p>
intent={
errors.next_number && touched.next_number && Intent.DANGER <ReferenceNumberFormContent />
}
{...getFieldProps('next_number')}
/>
</FormGroup>
</Col>
</Row>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={onClose}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
type="submit"
disabled={isSubmitting}
>
<T id={'submit'} />
</Button>
</div> </div>
</div>
</form> <div className={Classes.DIALOG_FOOTER}>
</div> <div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={onClose}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
type="submit"
disabled={isSubmitting}
>
<T id={'submit'} />
</Button>
</div>
</div>
</Form>
)}
</Formik>
); );
} }

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { FastField } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { FormGroup, InputGroup, Radio } from '@blueprintjs/core';
import { Row, Col, ErrorMessage } from 'components';
import { inputIntent } from 'utils';
/**
* Reference number form content.
*/
export default function ReferenceNumberFormContent() {
return (
<>
<FastField name={'mode'}>
{({ form, field, meta: { error, touched } }) => (
<Radio
label="Auto-incrementing invoice number."
value="auto-increment"
{...field}
/>
)}
</FastField>
<Row>
{/* ------------- Prefix ------------- */}
<Col xs={6}>
<FastField name={'prefix'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'prefix'} />}
className={'form-group--'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'prefix'} />}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
{/* ------------- Next number ------------- */}
<Col xs={6}>
<FastField name={'next_number'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'next_number'} />}
className={'form-group--'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'next_number'} />}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
<FastField name={'mode'}>
{({ form, field, meta: { error, touched } }) => (
<Radio label="Manual entring for this transaction." value="manual" {...field} />
)}
</FastField>
</>
);
}

View File

@@ -3,7 +3,7 @@ import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { FastField } from 'formik'; import { FastField } from 'formik';
import classNames from 'classnames'; import classNames from 'classnames';
import { Row, Col } from 'components'; import { Postbox, Row, Col } from 'components';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import { inputIntent } from 'utils'; import { inputIntent } from 'utils';
@@ -12,30 +12,32 @@ import { inputIntent } from 'utils';
export default function BillFormFooter() { export default function BillFormFooter() {
return ( return (
<div class={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div class={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Bill details'} defaultOpen={false}>
<Col md={8}> <Row>
<FastField name={'note'}> <Col md={8}>
{({ field, meta: { error, touched } }) => ( <FastField name={'note'}>
<FormGroup {({ field, meta: { error, touched } }) => (
label={<T id={'note'} />} <FormGroup
className={'form-group--note'} label={<T id={'note'} />}
intent={inputIntent({ error, touched })} className={'form-group--note'}
> intent={inputIntent({ error, touched })}
<TextArea growVertically={true} {...field} /> >
</FormGroup> <TextArea growVertically={true} {...field} />
)} </FormGroup>
</FastField> )}
</Col> </FastField>
</Col>
<Col md={4}> <Col md={4}>
<Dragzone <Dragzone
initialFiles={[]} initialFiles={[]}
// onDrop={onDropFiles} // onDrop={onDropFiles}
// onDeleteFile={onDropFiles} // onDeleteFile={onDropFiles}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -7,8 +7,8 @@ export const defaultBillEntry = {
index: 0, index: 0,
item_id: '', item_id: '',
rate: '', rate: '',
discount: 0, discount: '',
quantity: 1, quantity: '',
description: '', description: '',
}; };

View File

@@ -20,23 +20,23 @@ function PaymentMadeEntriesTable({
entries, entries,
// #withAlertsActions // #withAlertsActions
openAlert openAlert,
}) { }) {
const { const { paymentVendorId, isDueBillsFetching } = usePaymentMadeFormContext();
paymentVendorId,
isDueBillsFetching,
} = usePaymentMadeFormContext();
const columns = usePaymentMadeEntriesTableColumns(); const columns = usePaymentMadeEntriesTableColumns();
// Handle update data.
const handleUpdateData = useCallback((rowIndex, columnId, value) => {
const newRows = compose(
updateTableRow(rowIndex, columnId, value),
)(entries);
onUpdateData(newRows); // Handle update data.
}, [onUpdateData, entries]); const handleUpdateData = useCallback(
(rowIndex, columnId, value) => {
const newRows = compose(updateTableRow(rowIndex, columnId, value))(
entries,
);
onUpdateData(newRows);
},
[onUpdateData, entries],
);
// Detarmines the right no results message before selecting vendor and aftering // Detarmines the right no results message before selecting vendor and aftering
// selecting vendor id. // selecting vendor id.
@@ -44,15 +44,6 @@ function PaymentMadeEntriesTable({
? 'There is no payable bills for this vendor that can be applied for this payment' ? 'There is no payable bills for this vendor that can be applied for this payment'
: 'Please select a vendor to display all open bills for it.'; : 'Please select a vendor to display all open bills for it.';
// Handle clear all lines action.
const handleClearAllLines = () => {
const fullAmount = safeSumBy(entries, 'payment_amount');
if (fullAmount > 0) {
openAlert('clear-all-lines-payment-made');
}
}
return ( return (
<CloudLoadingIndicator isLoading={isDueBillsFetching}> <CloudLoadingIndicator isLoading={isDueBillsFetching}>
<DataTableEditable <DataTableEditable
@@ -66,21 +57,10 @@ function PaymentMadeEntriesTable({
updateData: handleUpdateData, updateData: handleUpdateData,
}} }}
noResults={noResultsMessage} noResults={noResultsMessage}
actions={ footer={true}
<Button
small={true}
className={'button--secondary button--clear-lines'}
onClick={handleClearAllLines}
>
<T id={'clear_all_lines'} />
</Button>
}
totalRow={true}
/> />
</CloudLoadingIndicator> </CloudLoadingIndicator>
); );
} }
export default compose( export default compose(withAlertActions)(PaymentMadeEntriesTable);
withAlertActions
)(PaymentMadeEntriesTable);

View File

@@ -3,7 +3,7 @@ import classNames from 'classnames';
import { FormGroup, TextArea } from '@blueprintjs/core'; import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { FastField } from 'formik'; import { FastField } from 'formik';
import { Row, Col } from 'components'; import { Postbox, Row, Col } from 'components';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
/** /**
@@ -12,21 +12,23 @@ import { CLASSES } from 'common/classes';
export default function PaymentMadeFooter() { export default function PaymentMadeFooter() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Payment made details'} defaultOpen={false}>
<Col md={8}> <Row>
{/* --------- Statement --------- */} <Col md={8}>
<FastField name={'customer_name'}> {/* --------- Statement --------- */}
{({ form, field, meta: { error, touched } }) => ( <FastField name={'customer_name'}>
<FormGroup {({ form, field, meta: { error, touched } }) => (
label={<T id={'statement'} />} <FormGroup
className={'form-group--statement'} label={<T id={'statement'} />}
> className={'form-group--statement'}
<TextArea growVertically={true} {...field} /> >
</FormGroup> <TextArea growVertically={true} {...field} />
)} </FormGroup>
</FastField> )}
</Col> </FastField>
</Row> </Col>
</Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -4,55 +4,57 @@ import { FormattedMessage as T } from 'react-intl';
import { FastField } from 'formik'; import { FastField } from 'formik';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { Row, Col } from 'components'; import { Row, Col, Postbox } from 'components';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import { inputIntent } from 'utils'; import { inputIntent } from 'utils';
/** /**
* Estimate form footer. * Estimate form footer.
*/ */
export default function EstiamteFormFooter({}) { export default function EstiamteFormFooter({}) {
return ( return (
<div class={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div class={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Estimate details'} defaultOpen={false}>
<Col md={8}> <Row>
{/* --------- Customer Note --------- */} <Col md={8}>
<FastField name={'note'}> {/* --------- Customer Note --------- */}
{({ form, field, meta: { error, touched } }) => ( <FastField name={'note'}>
<FormGroup {({ form, field, meta: { error, touched } }) => (
label={<T id={'customer_note'} />} <FormGroup
className={'form-group--customer_note'} label={<T id={'customer_note'} />}
intent={inputIntent({ error, touched })} className={'form-group--customer_note'}
> intent={inputIntent({ error, touched })}
<TextArea growVertically={true} {...field} /> >
</FormGroup> <TextArea growVertically={true} {...field} />
)} </FormGroup>
</FastField> )}
</FastField>
{/* --------- Terms and conditions --------- */} {/* --------- Terms and conditions --------- */}
<FastField name={'terms_conditions'}> <FastField name={'terms_conditions'}>
{({ field, meta: { error, touched } }) => ( {({ field, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'terms_conditions'} />} label={<T id={'terms_conditions'} />}
className={'form-group--terms_conditions'} className={'form-group--terms_conditions'}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
> >
<TextArea growVertically={true} {...field} /> <TextArea growVertically={true} {...field} />
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
</Col> </Col>
<Col md={4}> <Col md={4}>
<Dragzone <Dragzone
initialFiles={[]} initialFiles={[]}
// onDrop={handleDropFiles} // onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile} // onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -7,8 +7,8 @@ export const defaultEstimateEntry = {
index: 0, index: 0,
item_id: '', item_id: '',
rate: '', rate: '',
discount: 0, discount: '',
quantity: 1, quantity: '',
description: '', description: '',
}; };

View File

@@ -49,7 +49,7 @@ export const statusAccessor = (row) => (
*/ */
export function ActionsMenu({ export function ActionsMenu({
row: { original }, row: { original },
payload: { onEdit, onDeliver, onReject, onApprove, onDelete ,onDrawer }, payload: { onEdit, onDeliver, onReject, onApprove, onDelete, onDrawer },
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -101,9 +101,10 @@ export function ActionsMenu({
</Choose.When> </Choose.When>
</Choose> </Choose>
<MenuItem <MenuItem
text={formatMessage({ id: 'estimate_paper' })} icon={<Icon icon={'receipt-24'} iconSize={16} />}
onClick={() => onDrawer()} text={formatMessage({ id: 'estimate_paper' })}
/> onClick={() => onDrawer()}
/>
<MenuItem <MenuItem
text={formatMessage({ id: 'delete_estimate' })} text={formatMessage({ id: 'delete_estimate' })}
intent={Intent.DANGER} intent={Intent.DANGER}

View File

@@ -123,7 +123,11 @@ function InvoiceForm({
}; };
// Handle the request error. // Handle the request error.
const onError = ({ response: { data: { errors } } }) => { const onError = ({
response: {
data: { errors },
},
}) => {
if (errors) { if (errors) {
handleErrors(errors, { setErrors }); handleErrors(errors, { setErrors });
} }
@@ -146,6 +150,7 @@ function InvoiceForm({
)} )}
> >
<Formik <Formik
enableReinitialize={true}
validationSchema={ validationSchema={
isNewMode ? CreateInvoiceFormSchema : EditInvoiceFormSchema isNewMode ? CreateInvoiceFormSchema : EditInvoiceFormSchema
} }
@@ -154,10 +159,7 @@ function InvoiceForm({
> >
<Form> <Form>
<InvoiceFormHeader /> <InvoiceFormHeader />
<InvoiceItemsEntriesEditorField />
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<InvoiceItemsEntriesEditorField />
</div>
<InvoiceFormFooter /> <InvoiceFormFooter />
<InvoiceFloatingActions /> <InvoiceFloatingActions />
<InvoiceFormDialogs /> <InvoiceFormDialogs />

View File

@@ -11,6 +11,9 @@ export default function InvoiceFormDialogs() {
// Update the form once the invoice number form submit confirm. // Update the form once the invoice number form submit confirm.
const handleInvoiceNumberFormConfirm = (values) => { const handleInvoiceNumberFormConfirm = (values) => {
debugger;
console.log(values, 'XX');
setFieldValue( setFieldValue(
'invoice_no', 'invoice_no',
transactionNumber(values.number_prefix, values.next_number), transactionNumber(values.number_prefix, values.next_number),

View File

@@ -4,7 +4,7 @@ import classNames from 'classnames';
import { FormGroup, TextArea } from '@blueprintjs/core'; import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { Row, Col } from 'components'; import { Row, Col, Postbox } from 'components';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import { inputIntent } from 'utils'; import { inputIntent } from 'utils';
@@ -12,44 +12,46 @@ import { inputIntent } from 'utils';
export default function InvoiceFormFooter() { export default function InvoiceFormFooter() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Invoice details'} defaultOpen={false}>
<Col md={8}> <Row>
{/* --------- Invoice message --------- */} <Col md={8}>
<FastField name={'invoice_message'}> {/* --------- Invoice message --------- */}
{({ field, meta: { error, touched } }) => ( <FastField name={'invoice_message'}>
<FormGroup {({ field, meta: { error, touched } }) => (
label={<T id={'invoice_message'} />} <FormGroup
className={'form-group--invoice_message'} label={<T id={'invoice_message'} />}
intent={inputIntent({ error, touched })} className={'form-group--invoice_message'}
> intent={inputIntent({ error, touched })}
<TextArea growVertically={true} {...field} /> >
</FormGroup> <TextArea growVertically={true} {...field} />
)} </FormGroup>
</FastField> )}
</FastField>
{/* --------- Terms and conditions --------- */} {/* --------- Terms and conditions --------- */}
<FastField name={'terms_conditions'}> <FastField name={'terms_conditions'}>
{({ field, meta: { error, touched } }) => ( {({ field, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'terms_conditions'} />} label={<T id={'terms_conditions'} />}
className={'form-group--terms_conditions'} className={'form-group--terms_conditions'}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
> >
<TextArea growVertically={true} {...field} /> <TextArea growVertically={true} {...field} />
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
</Col> </Col>
<Col md={4}> <Col md={4}>
<Dragzone <Dragzone
initialFiles={[]} initialFiles={[]}
// onDrop={handleDropFiles} // onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile} // onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -45,7 +45,7 @@ function InvoiceFormHeaderFields({
<FormGroup <FormGroup
label={<T id={'customer_name'} />} label={<T id={'customer_name'} />}
inline={true} inline={true}
className={classNames('form-group--customer-name', CLASSES.FILL)} className={classNames('form-group--customer-name', 'form-group--select-list', CLASSES.FILL)}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'customer_id'} />} helperText={<ErrorMessage name={'customer_id'} />}

View File

@@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import InvoiceForm from './InvoiceForm';
import 'style/pages/SaleInvoice/PageForm.scss'; import 'style/pages/SaleInvoice/PageForm.scss';
import InvoiceForm from './InvoiceForm';
import { InvoiceFormProvider } from './InvoiceFormProvider'; import { InvoiceFormProvider } from './InvoiceFormProvider';
/** /**

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React, { useCallback } from 'react';
import { FastField } from 'formik'; import { FastField } from 'formik';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable'; import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { useInvoiceFormContext } from './InvoiceFormProvider'; import { useInvoiceFormContext } from './InvoiceFormProvider';
@@ -8,20 +10,22 @@ import { useInvoiceFormContext } from './InvoiceFormProvider';
*/ */
export default function InvoiceItemsEntriesEditorField() { export default function InvoiceItemsEntriesEditorField() {
const { items } = useInvoiceFormContext(); const { items } = useInvoiceFormContext();
return ( return (
<FastField name={'entries'}> <div className={classNames(CLASSES.PAGE_FORM_BODY)}>
{({ form, field: { value }, meta: { error, touched } }) => ( <FastField name={'entries'}>
<ItemsEntriesTable {({ form, field: { value }, meta: { error, touched } }) => (
entries={value} <ItemsEntriesTable
onUpdateData={(entries) => { entries={value}
form.setFieldValue('entries', entries); onUpdateData={(entries) => {
}} form.setFieldValue('entries', entries);
items={items} }}
errors={error} items={items}
linesNumber={4} errors={error}
/> linesNumber={4}
)} />
</FastField> )}
</FastField>
</div>
); );
} }

View File

@@ -9,8 +9,8 @@ export const defaultInvoiceEntry = {
index: 0, index: 0,
item_id: '', item_id: '',
rate: '', rate: '',
discount: 0, discount: '',
quantity: 1, quantity: '',
description: '', description: '',
total: 0, total: 0,
}; };

View File

@@ -118,6 +118,7 @@ export function ActionsMenu({
/> />
</If> </If>
<MenuItem <MenuItem
icon={<Icon icon={'receipt-24'} iconSize={16} />}
text={formatMessage({ id: 'invoice_paper' })} text={formatMessage({ id: 'invoice_paper' })}
onClick={() => onDrawer()} onClick={() => onDrawer()}
/> />

View File

@@ -165,7 +165,7 @@ function PaymentReceiveForm({
<PaymentReceiveFormFooter /> <PaymentReceiveFormFooter />
<PaymentReceiveFloatingActions /> <PaymentReceiveFloatingActions />
{/* Alerts & Dialogs */} {/* ------- Alerts & Dialogs ------- */}
<PaymentReceiveFormAlerts /> <PaymentReceiveFormAlerts />
<PaymentReceiveFormDialogs /> <PaymentReceiveFormDialogs />
</PaymentReceiveInnerProvider> </PaymentReceiveInnerProvider>

View File

@@ -3,7 +3,7 @@ import classNames from 'classnames';
import { FormGroup, TextArea } from '@blueprintjs/core'; import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { FastField } from 'formik'; import { FastField } from 'formik';
import { Row, Col } from 'components'; import { Row, Col, Postbox } from 'components';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
/** /**
@@ -12,21 +12,23 @@ import { CLASSES } from 'common/classes';
export default function PaymentReceiveFormFooter({ getFieldProps }) { export default function PaymentReceiveFormFooter({ getFieldProps }) {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Payment receive details'} defaultOpen={false}>
<Col md={8}> <Row>
{/* --------- Statement --------- */} <Col md={8}>
<FastField name={'statement'}> {/* --------- Statement --------- */}
{({ form, field, meta: { error, touched } }) => ( <FastField name={'statement'}>
<FormGroup {({ form, field, meta: { error, touched } }) => (
label={<T id={'statement'} />} <FormGroup
className={'form-group--statement'} label={<T id={'statement'} />}
> className={'form-group--statement'}
<TextArea growVertically={true} {...field} /> >
</FormGroup> <TextArea growVertically={true} {...field} />
)} </FormGroup>
</FastField> )}
</Col> </FastField>
</Row> </Col>
</Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -45,15 +45,6 @@ function PaymentReceiveItemsTable({
onUpdateData(newRows); onUpdateData(newRows);
}, [entries, onUpdateData]); }, [entries, onUpdateData]);
// Handle click clear all lines button.
const handleClickClearAllLines = () => {
const fullAmount = safeSumBy(entries, 'payment_amount');
if (fullAmount > 0) {
openAlert('clear-all-lines-payment-receive');
}
};
return ( return (
<CloudLoadingIndicator isLoading={isDueInvoicesFetching}> <CloudLoadingIndicator isLoading={isDueInvoicesFetching}>
<DataTableEditable <DataTableEditable
@@ -67,16 +58,7 @@ function PaymentReceiveItemsTable({
updateData: handleUpdateData, updateData: handleUpdateData,
}} }}
noResults={noResultsMessage} noResults={noResultsMessage}
actions={ footer={true}
<Button
small={true}
className={'button--secondary button--clear-lines'}
onClick={handleClickClearAllLines}
>
<T id={'clear_all_lines'} />
</Button>
}
totalRow={true}
/> />
</CloudLoadingIndicator> </CloudLoadingIndicator>
); );

View File

@@ -88,12 +88,13 @@ export const usePaymentReceiveEntriesColumns = () => {
disableSortBy: true, disableSortBy: true,
disableResizing: true, disableResizing: true,
width: 250, width: 250,
className: 'date'
}, },
{ {
Header: formatMessage({ id: 'invocie_number' }), Header: formatMessage({ id: 'invocie_number' }),
accessor: InvNumberCellAccessor, accessor: InvNumberCellAccessor,
disableSortBy: true, disableSortBy: true,
className: '', className: 'invoice_number',
}, },
{ {
Header: formatMessage({ id: 'invoice_amount' }), Header: formatMessage({ id: 'invoice_amount' }),
@@ -102,7 +103,7 @@ export const usePaymentReceiveEntriesColumns = () => {
Cell: MoneyTableCell, Cell: MoneyTableCell,
disableSortBy: true, disableSortBy: true,
width: 100, width: 100,
className: '', className: 'invoice_amount',
}, },
{ {
Header: formatMessage({ id: 'amount_due' }), Header: formatMessage({ id: 'amount_due' }),
@@ -111,7 +112,7 @@ export const usePaymentReceiveEntriesColumns = () => {
Cell: MoneyTableCell, Cell: MoneyTableCell,
disableSortBy: true, disableSortBy: true,
width: 150, width: 150,
className: '', className: 'amount_due',
}, },
{ {
Header: formatMessage({ id: 'payment_amount' }), Header: formatMessage({ id: 'payment_amount' }),
@@ -120,7 +121,7 @@ export const usePaymentReceiveEntriesColumns = () => {
Footer: PaymentAmountFooterCell, Footer: PaymentAmountFooterCell,
disableSortBy: true, disableSortBy: true,
width: 150, width: 150,
className: '', className: 'payment_amount',
}, },
], ],
[formatMessage], [formatMessage],

View File

@@ -35,6 +35,7 @@ export function ActionsMenu({
onClick={safeCallback(onEdit, paymentReceive)} onClick={safeCallback(onEdit, paymentReceive)}
/> />
<MenuItem <MenuItem
icon={<Icon icon={'receipt-24'} iconSize={16} />}
text={formatMessage({ id: 'payment_receive_paper' })} text={formatMessage({ id: 'payment_receive_paper' })}
onClick={() => onDrawer()} onClick={() => onDrawer()}
/> />

View File

@@ -10,11 +10,9 @@ import { CLASSES } from 'common/classes';
import { ERROR } from 'common/errors'; import { ERROR } from 'common/errors';
import { import {
EditReceiptFormSchema, EditReceiptFormSchema,
CreateReceiptFormSchema, CreateReceiptFormSchema,
} from './ReceiptForm.schema'; } from './ReceiptForm.schema';
import 'style/pages/SaleReceipt/PageForm.scss';
import { useReceiptFormContext } from './ReceiptFormProvider'; import { useReceiptFormContext } from './ReceiptFormProvider';
import ReceiptFromHeader from './ReceiptFormHeader'; import ReceiptFromHeader from './ReceiptFormHeader';

View File

@@ -3,51 +3,53 @@ import { FormGroup, TextArea } from '@blueprintjs/core';
import { FastField } from 'formik'; import { FastField } from 'formik';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { Dragzone, Row, Col } from 'components'; import { Dragzone, Postbox, Row, Col } from 'components';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { inputIntent } from 'utils'; import { inputIntent } from 'utils';
export default function ReceiptFormFooter({}) { export default function ReceiptFormFooter({}) {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row> <Postbox title={'Invoice details'} defaultOpen={false}>
<Col md={8}> <Row>
{/* --------- Receipt message --------- */} <Col md={8}>
<FastField name={'receipt_message'}> {/* --------- Receipt message --------- */}
{({ field, meta: { error, touched } }) => ( <FastField name={'receipt_message'}>
<FormGroup {({ field, meta: { error, touched } }) => (
label={<T id={'receipt_message'} />} <FormGroup
className={'form-group--receipt_message'} label={<T id={'receipt_message'} />}
intent={inputIntent({ error, touched })} className={'form-group--receipt_message'}
> intent={inputIntent({ error, touched })}
<TextArea growVertically={true} {...field} /> >
</FormGroup> <TextArea growVertically={true} {...field} />
)} </FormGroup>
</FastField> )}
</FastField>
{/* --------- Statement--------- */} {/* --------- Statement--------- */}
<FastField name={'statement'}> <FastField name={'statement'}>
{({ field, meta: { error, touched } }) => ( {({ field, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'statement'} />} label={<T id={'statement'} />}
className={'form-group--statement'} className={'form-group--statement'}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
> >
<TextArea growVertically={true} {...field} /> <TextArea growVertically={true} {...field} />
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
</Col> </Col>
<Col md={4}> <Col md={4}>
<Dragzone <Dragzone
initialFiles={[]} initialFiles={[]}
// onDrop={handleDropFiles} // onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile} // onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</Postbox>
</div> </div>
); );
} }

View File

@@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import 'style/pages/SaleReceipt/PageForm.scss';
import ReceiptFrom from './ReceiptForm'; import ReceiptFrom from './ReceiptForm';
import { ReceiptFormProvider } from './ReceiptFormProvider'; import { ReceiptFormProvider } from './ReceiptFormProvider';

View File

@@ -7,7 +7,7 @@ export const defaultReceiptEntry = {
index: 0, index: 0,
item_id: '', item_id: '',
rate: '', rate: '',
discount: 0, discount: '',
quantity: '', quantity: '',
description: '', description: '',
}; };

View File

@@ -15,7 +15,7 @@ import { Choose, Money, Icon, If } from 'components';
import moment from 'moment'; import moment from 'moment';
export function ActionsMenu({ export function ActionsMenu({
payload: { onEdit, onDelete, onClose ,onDrawer }, payload: { onEdit, onDelete, onClose, onDrawer },
row: { original: receipt }, row: { original: receipt },
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -39,9 +39,10 @@ export function ActionsMenu({
/> />
</If> </If>
<MenuItem <MenuItem
text={formatMessage({ id: 'receipt_paper' })} icon={<Icon icon={'receipt-24'} iconSize={16} />}
onClick={() => onDrawer()} text={formatMessage({ id: 'receipt_paper' })}
/> onClick={() => onDrawer()}
/>
<MenuItem <MenuItem
text={formatMessage({ id: 'delete_receipt' })} text={formatMessage({ id: 'delete_receipt' })}
intent={Intent.DANGER} intent={Intent.DANGER}

View File

@@ -13,9 +13,11 @@ export function useCreateInvoice(props) {
return useMutation((values) => apiRequest.post('sales/invoices', values), { return useMutation((values) => apiRequest.post('sales/invoices', values), {
onSuccess: (values) => { onSuccess: (values) => {
queryClient.invalidateQueries('SALE_INVOICES'); queryClient.invalidateQueries('SALE_INVOICES');
queryClient.invalidateQueries(['SETTINGS', 'INVOICES']);
queryClient.invalidateQueries('CUSTOMERS'); queryClient.invalidateQueries('CUSTOMERS');
queryClient.invalidateQueries(['CUSTOMER', values.customer_id]); queryClient.invalidateQueries(['CUSTOMER', values.customer_id]);
queryClient.invalidateQueries(['SETTINGS', 'INVOICES']);
}, },
...props, ...props,
}); });

View File

@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from 'react-query'; import { useQuery, useMutation, useQueryClient } from 'react-query';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import useApiRequest from '../useRequest'; import useApiRequest from '../useRequest';
@@ -22,7 +23,7 @@ function useSettingsQuery(key, query, props) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
return useQuery( const state = useQuery(
key, key,
() => apiRequest.get('settings', { params: query }), () => apiRequest.get('settings', { params: query }),
{ {
@@ -33,12 +34,17 @@ function useSettingsQuery(key, query, props) {
settings: [], settings: [],
}, },
}, },
onSuccess: (settings) => {
dispatch({ type: t.SETTING_SET, options: settings });
},
...props, ...props,
}, },
); );
useEffect(() => {
if (typeof state.data !== 'undefined') {
dispatch({ type: t.SETTING_SET, options: state.data });
}
}, [state.data, dispatch]);
return state.data;
} }
/** /**

View File

@@ -4,13 +4,7 @@ import { formatMessage } from 'services/intl';
// const BASE_URL = '/dashboard'; // const BASE_URL = '/dashboard';
export default [ export default [
// Homepage
{
path: `/homepage`,
component: lazy(() => import('containers/Homepage/Homepage')),
breadcrumb: 'Home',
pageTitle: 'Homepage',
},
// Accounts. // Accounts.
{ {
path: `/accounts`, path: `/accounts`,
@@ -73,6 +67,7 @@ export default [
{ {
path: `/items/:id/edit`, path: `/items/:id/edit`,
component: lazy(() => import('containers/Items/ItemFormPage')), component: lazy(() => import('containers/Items/ItemFormPage')),
name: 'item-edit',
breadcrumb: 'Edit Item', breadcrumb: 'Edit Item',
pageTitle: formatMessage({ id: 'edit_item' }), pageTitle: formatMessage({ id: 'edit_item' }),
backLink: true, backLink: true,
@@ -80,6 +75,7 @@ export default [
{ {
path: `/items/new`, path: `/items/new`,
component: lazy(() => import('containers/Items/ItemFormPage')), component: lazy(() => import('containers/Items/ItemFormPage')),
name: 'item-new',
breadcrumb: 'New Item', breadcrumb: 'New Item',
hotkey: 'ctrl+shift+w', hotkey: 'ctrl+shift+w',
pageTitle: formatMessage({ id: 'new_item' }), pageTitle: formatMessage({ id: 'new_item' }),
@@ -233,6 +229,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Customers/CustomerForm/CustomerFormPage'), import('containers/Customers/CustomerForm/CustomerFormPage'),
), ),
name: 'customer-edit',
breadcrumb: 'Edit Customer', breadcrumb: 'Edit Customer',
pageTitle: formatMessage({ id: 'edit_customer' }), pageTitle: formatMessage({ id: 'edit_customer' }),
backLink: true, backLink: true,
@@ -242,6 +239,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Customers/CustomerForm/CustomerFormPage'), import('containers/Customers/CustomerForm/CustomerFormPage'),
), ),
name: 'customer-new',
breadcrumb: 'New Customer', breadcrumb: 'New Customer',
hotkey: 'ctrl+shift+c', hotkey: 'ctrl+shift+c',
pageTitle: formatMessage({ id: 'new_customer' }), pageTitle: formatMessage({ id: 'new_customer' }),
@@ -263,6 +261,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Vendors/VendorForm/VendorFormPage'), import('containers/Vendors/VendorForm/VendorFormPage'),
), ),
name: 'vendor-edit',
breadcrumb: 'Edit Vendor', breadcrumb: 'Edit Vendor',
pageTitle: formatMessage({ id: 'edit_vendor' }), pageTitle: formatMessage({ id: 'edit_vendor' }),
backLink: true, backLink: true,
@@ -272,6 +271,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Vendors/VendorForm/VendorFormPage'), import('containers/Vendors/VendorForm/VendorFormPage'),
), ),
name: 'vendor-new',
breadcrumb: 'New Vendor', breadcrumb: 'New Vendor',
hotkey: 'ctrl+shift+v', hotkey: 'ctrl+shift+v',
pageTitle: formatMessage({ id: 'new_vendor' }), pageTitle: formatMessage({ id: 'new_vendor' }),
@@ -293,6 +293,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'), import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'),
), ),
name: 'estimate-edit',
breadcrumb: 'Edit', breadcrumb: 'Edit',
pageTitle: formatMessage({ id: 'edit_estimate' }), pageTitle: formatMessage({ id: 'edit_estimate' }),
backLink: true, backLink: true,
@@ -303,6 +304,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'), import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'),
), ),
name: 'estimate-new',
breadcrumb: 'New Estimate', breadcrumb: 'New Estimate',
hotkey: 'ctrl+shift+e', hotkey: 'ctrl+shift+e',
pageTitle: formatMessage({ id: 'new_estimate' }), pageTitle: formatMessage({ id: 'new_estimate' }),
@@ -314,6 +316,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Sales/Estimates/EstimatesLanding/EstimatesList'), import('containers/Sales/Estimates/EstimatesLanding/EstimatesList'),
), ),
name: 'estimates-list',
breadcrumb: 'Estimates List', breadcrumb: 'Estimates List',
hotkey: 'shift+e', hotkey: 'shift+e',
pageTitle: formatMessage({ id: 'estimates_list' }), pageTitle: formatMessage({ id: 'estimates_list' }),
@@ -325,6 +328,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Sales/Invoices/InvoiceForm/InvoiceFormPage'), import('containers/Sales/Invoices/InvoiceForm/InvoiceFormPage'),
), ),
name: 'invoice-edit',
breadcrumb: 'Edit', breadcrumb: 'Edit',
pageTitle: formatMessage({ id: 'edit_invoice' }), pageTitle: formatMessage({ id: 'edit_invoice' }),
sidebarShrink: true, sidebarShrink: true,
@@ -335,6 +339,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Sales/Invoices/InvoiceForm/InvoiceFormPage'), import('containers/Sales/Invoices/InvoiceForm/InvoiceFormPage'),
), ),
name: 'invoice-new',
breadcrumb: 'New Invoice', breadcrumb: 'New Invoice',
hotkey: 'ctrl+shift+i', hotkey: 'ctrl+shift+i',
pageTitle: formatMessage({ id: 'new_invoice' }), pageTitle: formatMessage({ id: 'new_invoice' }),
@@ -357,6 +362,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'), import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'),
), ),
name: 'receipt-edit',
breadcrumb: 'Edit', breadcrumb: 'Edit',
pageTitle: formatMessage({ id: 'edit_receipt' }), pageTitle: formatMessage({ id: 'edit_receipt' }),
backLink: true, backLink: true,
@@ -367,6 +373,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'), import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'),
), ),
name: 'receipt-new',
breadcrumb: 'New Receipt', breadcrumb: 'New Receipt',
hotkey: 'ctrl+shift+r', hotkey: 'ctrl+shift+r',
pageTitle: formatMessage({ id: 'new_receipt' }), pageTitle: formatMessage({ id: 'new_receipt' }),
@@ -391,6 +398,7 @@ export default [
'containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormPage' 'containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormPage'
), ),
), ),
name: 'payment-receive-edit',
breadcrumb: 'Edit', breadcrumb: 'Edit',
pageTitle: formatMessage({ id: 'edit_payment_receive' }), pageTitle: formatMessage({ id: 'edit_payment_receive' }),
backLink: true, backLink: true,
@@ -403,6 +411,7 @@ export default [
'containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormPage' 'containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormPage'
), ),
), ),
name: 'payment-receive-new',
breadcrumb: 'New Payment Receive', breadcrumb: 'New Payment Receive',
pageTitle: formatMessage({ id: 'new_payment_receive' }), pageTitle: formatMessage({ id: 'new_payment_receive' }),
backLink: true, backLink: true,
@@ -425,6 +434,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Purchases/Bills/BillForm/BillFormPage'), import('containers/Purchases/Bills/BillForm/BillFormPage'),
), ),
name: 'bill-edit',
breadcrumb: 'Edit', breadcrumb: 'Edit',
pageTitle: formatMessage({ id: 'edit_bill' }), pageTitle: formatMessage({ id: 'edit_bill' }),
sidebarShrink: true, sidebarShrink: true,
@@ -435,6 +445,7 @@ export default [
component: lazy(() => component: lazy(() =>
import('containers/Purchases/Bills/BillForm/BillFormPage'), import('containers/Purchases/Bills/BillForm/BillFormPage'),
), ),
name: 'bill-new',
breadcrumb: 'New Bill', breadcrumb: 'New Bill',
hotkey: 'ctrl+shift+b', hotkey: 'ctrl+shift+b',
pageTitle: formatMessage({ id: 'new_bill' }), pageTitle: formatMessage({ id: 'new_bill' }),
@@ -465,6 +476,7 @@ export default [
'containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormPage' 'containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormPage'
), ),
), ),
name: 'payment-made-edit',
breadcrumb: 'Edit', breadcrumb: 'Edit',
pageTitle: formatMessage({ id: 'edit_payment_made' }), pageTitle: formatMessage({ id: 'edit_payment_made' }),
sidebarShrink: true, sidebarShrink: true,
@@ -477,6 +489,7 @@ export default [
'containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormPage' 'containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormPage'
), ),
), ),
name: 'payment-made-new',
breadcrumb: 'New Payment Made', breadcrumb: 'New Payment Made',
pageTitle: formatMessage({ id: 'new_payment_made' }), pageTitle: formatMessage({ id: 'new_payment_made' }),
sidebarShrink: true, sidebarShrink: true,
@@ -492,4 +505,11 @@ export default [
breadcrumb: 'Payment Made List', breadcrumb: 'Payment Made List',
pageTitle: formatMessage({ id: 'payment_made_list' }), pageTitle: formatMessage({ id: 'payment_made_list' }),
}, },
// Homepage
{
path: `/`,
component: lazy(() => import('containers/Homepage/Homepage')),
breadcrumb: 'Home',
pageTitle: 'Homepage',
},
]; ];

View File

@@ -396,4 +396,8 @@ export default {
path: ['M9,5v2h6.59L4,18.59L5.41,20L17,8.41V15h2V5H9'], path: ['M9,5v2h6.59L4,18.59L5.41,20L17,8.41V15h2V5H9'],
viewBox: '0 0 24 24', viewBox: '0 0 24 24',
}, },
'receipt-24': {
path: ['M19.5 3.5L18 2l-1.5 1.5L15 2l-1.5 1.5L12 2l-1.5 1.5L9 2 7.5 3.5 6 2 4.5 3.5 3 2v20l1.5-1.5L6 22l1.5-1.5L9 22l1.5-1.5L12 22l1.5-1.5L15 22l1.5-1.5L18 22l1.5-1.5L21 22V2l-1.5 1.5zM19 19.09H5V4.91h14v14.18zM6 15h12v2H6zm0-4h12v2H6zm0-4h12v2H6z'],
viewBox: '0 0 24 24',
}
}; };

View File

@@ -24,6 +24,7 @@
@import 'components/Toast'; @import 'components/Toast';
@import 'components/PageForm'; @import 'components/PageForm';
@import 'components/Tooltip'; @import 'components/Tooltip';
@import 'components/Postbox';
// Pages // Pages
@import 'pages/view-form'; @import 'pages/view-form';
@@ -99,4 +100,4 @@ body.hide-scrollbar .Pane2{
.bp3-progress-bar.bp3-intent-primary .bp3-progress-meter{ .bp3-progress-bar.bp3-intent-primary .bp3-progress-meter{
background-color: #0066ff; background-color: #0066ff;
} }

View File

@@ -1,5 +1,6 @@
.big-amount{ .big-amount{
text-align: right;
&__label{ &__label{
color: #5d6f90; color: #5d6f90;
@@ -11,7 +12,7 @@
margin: 0; margin: 0;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
font-weight: 400; font-weight: 500;
margin-top: 6px; margin-top: 6px;
color: #343463; color: #343463;
line-height: 1; line-height: 1;

View File

@@ -1,14 +1,16 @@
.datatable-editor { .datatable-editor {
.bp3-form-group { .bp3-form-group {
margin-bottom: 0; margin-bottom: 0;
} }
.table { .table {
border: 1px solid #d2dce2; border: 1px solid #d2dce2;
border-left: transparent; border-left: transparent;
background-color: #FFF;
.th, .th,
.td { .td {
border-left: 1px solid #e2e2e2; border-left: 1px dashed #e2e2e2;
&.index { &.index {
text-align: center; text-align: center;
@@ -29,7 +31,8 @@
background-color: #f0f2f8; background-color: #f0f2f8;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: #1c1940; color: #415060;
border-bottom: 1px solid #d2dce2;
&.index > div { &.index > div {
width: 100%; width: 100%;
@@ -39,13 +42,14 @@
.tbody { .tbody {
.tr .td { .tr .td {
padding: 5px; padding: 4px;
border-bottom: 0; border-bottom: 0;
border-top: 1px dashed #aaa; border-top: 1px solid #d8d8d8;
min-height: 42px; min-height: 40px;
&.index { &.index {
background-color: #f0f2f8; background-color: #f0f2f8;
color: #718294;
> span { > span {
margin-top: auto; margin-top: auto;
@@ -126,6 +130,12 @@
} }
} }
} }
.tfooter{
.td{
min-height: 38px;
}
}
.th { .th {
color: #444; color: #444;
font-weight: 600; font-weight: 600;
@@ -142,6 +152,23 @@
margin: 0; margin: 0;
} }
} }
.tbody,
.thead,
.tfooter{
// .total,
.quantity,
.rate,
.discount{
&,
input{
text-align: right;
}
}
}
} }
.table { .table {

View File

@@ -12,7 +12,7 @@
width: 100%; width: 100%;
background: #fff; background: #fff;
padding: 14px 18px; padding: 14px 18px;
border-top: 1px solid rgba(0, 0, 0, 0.05); border-top: 1px solid rgb(210, 221, 226);
box-shadow: 0px -1px 4px 0px rgba(0, 0, 0, 0.05); box-shadow: 0px -1px 4px 0px rgba(0, 0, 0, 0.05);
.bp3-button-group{ .bp3-button-group{
@@ -33,6 +33,14 @@
&--strip { &--strip {
#{$self}__header-fields { #{$self}__header-fields {
width: 85%; width: 85%;
.bp3-form-group{
margin-bottom: 16px;
}
> .bp3-form-group:last-of-type{
margin-bottom: 0;
}
} }
#{$self}__body, #{$self}__body,
#{$self}__footer { #{$self}__footer {
@@ -40,8 +48,8 @@
} }
#{$self}__header { #{$self}__header {
background-color: #fbfbfb; background-color: #FFF;
padding: 30px 20px 0; padding: 25px 32px;
border-bottom: 1px solid #d2dce2; border-bottom: 1px solid #d2dce2;
.bp3-form-group.bp3-inline label.bp3-label { .bp3-form-group.bp3-inline label.bp3-label {
@@ -50,15 +58,13 @@
} }
#{$self}__body { #{$self}__body {
padding-top: 15px; padding: 18px 32px 0;
padding-left: 20px;
padding-right: 20px;
} }
#{$self}__footer { #{$self}__footer {
margin: 25px 0 0 0; margin: 20px 0 0 0;
padding-left: 20px; padding-left: 32px;
padding-right: 20px; padding-right: 32px;
label.bp3-label{ label.bp3-label{
font-weight: 500; font-weight: 500;

View File

@@ -0,0 +1,48 @@
.postbox {
border: 1px solid #d2dce2;
background: #FFF;
&__header {
border-bottom: 1px solid #d2dde2;
height: 38px;
padding-left: 18px;
align-items: center;
display: flex;
}
&__title {
vertical-align: middle;
font-weight: 600;
font-size: 14px;
}
&.is-toggable .postbox__header {
cursor: pointer;
}
&__toggle-indicator {
margin-left: auto;
width: 40px;
text-align: center;
display: flex;
&:before {
content: "";
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 6px solid #8ca0b3;
margin: auto;
}
}
&__title {}
&__content {
&-inner {
padding: 20px;
}
}
}

View File

@@ -4,7 +4,7 @@
} }
.path-1, .path-1,
.path-13 { .path-13 {
fill: #4f5861; fill: #2d95fd;
} }
} }

View File

@@ -49,7 +49,7 @@ label.bp3-label {
background: #e9ecef; background: #e9ecef;
} }
.bp3-form-group.bp3-intent-danger & { .bp3-form-group.bp3-intent-danger > & {
box-shadow: 0 0 0 transparent; box-shadow: 0 0 0 transparent;
border-color: #db3737; border-color: #db3737;

View File

@@ -1,7 +1,17 @@
.dashboard__insider--bill-form{
background-color: #FFF; body.page-bill-new,
body.page-bill-edit{
.dashboard__footer{
display: none;
}
} }
.dashboard__insider--bill-form{
padding-bottom: 64px;
}
.page-form--bill{ .page-form--bill{
$self: '.page-form'; $self: '.page-form';

View File

@@ -1,5 +1,18 @@
@import '../../Base.scss'; @import '../../Base.scss';
body.page-customer-new,
body.page-customer-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--customer-form{
padding-bottom: 64px;
}
.page-form--customer { .page-form--customer {
$self: '.page-form'; $self: '.page-form';
padding: 20px; padding: 20px;

View File

@@ -11,7 +11,7 @@ $dashboard-views-bar-height: 45px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
background-color: #fff; background-color: #fff;
border-bottom: 1px solid #dddee3; border-bottom: 1px solid #c7d5db;
&-right, &-right,
&-left { &-left {
@@ -137,7 +137,11 @@ $dashboard-views-bar-height: 45px;
} }
&__actions-bar { &__actions-bar {
border-bottom: 2px solid #eaeaea; border-bottom: 2px solid #e1e2e8;
.bp3-navbar-divider{
border-left-color: rgb(199, 214, 219);
}
.#{$ns}-navbar { .#{$ns}-navbar {
box-shadow: none; box-shadow: none;
@@ -493,3 +497,5 @@ $dashboard-views-bar-height: 45px;
} }
} }
} }

View File

@@ -1,8 +1,5 @@
.dashboard__insider--expense-form{
background-color: #fff;
}
.dashboard__insider--expenses{
.dashboard__insider--expenses{
.bigcapital-datatable{ .bigcapital-datatable{

View File

@@ -1,3 +1,16 @@
body.page-item-new,
body.page-item-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--item-form{
padding-bottom: 64px;
}
.page-form--item { .page-form--item {
$self: '.page-form'; $self: '.page-form';
padding: 20px; padding: 20px;

View File

@@ -9,7 +9,7 @@
border-bottom: 1px solid #e7e7e7; border-bottom: 1px solid #e7e7e7;
> span { > span {
font-weight: 500; font-weight: 600;
} }
} }
} }

View File

@@ -3,6 +3,12 @@
$self: '.page-form'; $self: '.page-form';
#{$self}__header{ #{$self}__header{
display: flex;
&-fields {
flex: 1 0 0;
}
.bp3-label{ .bp3-label{
min-width: 140px; min-width: 140px;
} }
@@ -28,10 +34,3 @@
} }
} }
} }
.dashboard__insider{
&--make-journal-page{
background: #fff;
}
}

View File

@@ -1,5 +1,14 @@
body.page-payment-made-new,
body.page-payment-made-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--payment-made{ .dashboard__insider--payment-made{
background-color: #FFF; padding-bottom: 64px;
} }
.page-form--payment-made { .page-form--payment-made {

View File

@@ -1,26 +1,37 @@
body.page-payment-receive-new,
body.page-payment-receive-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--payment-receive-form{ .dashboard__insider--payment-receive-form{
background-color: #FFF; padding-bottom: 64px;
} }
.page-form--payment-receive { .page-form--payment-receive {
$self: '.page-form'; $self: '.page-form';
#{$self}__header{ #{$self}__header {
.bp3-label{ .bp3-label {
min-width: 160px; min-width: 160px;
} }
.bp3-form-content{
.bp3-form-content {
width: 100%; width: 100%;
} }
.bp3-form-group{ .bp3-form-group {
margin-bottom: 18px; margin-bottom: 18px;
&.bp3-inline{ &.bp3-inline {
max-width: 470px; max-width: 470px;
} }
button.receive-full-amount{
button.receive-full-amount {
width: auto; width: auto;
padding: 0; padding: 0;
min-height: auto; min-height: auto;
@@ -29,28 +40,28 @@
background-color: transparent; background-color: transparent;
color: #0052cc; color: #0052cc;
&:hover{ &:hover {
text-decoration: underline; text-decoration: underline;
} }
} }
} }
} }
#{$self}__primary-section{ #{$self}__primary-section {
display: flex; display: flex;
padding-bottom: 2px; padding-bottom: 2px;
} }
#{$self}__big-numbers{ #{$self}__big-numbers {
margin-left: auto; margin-left: auto;
padding-right: 10px; padding-right: 10px;
} }
.datatable-editor{ .datatable-editor {
.table .tbody{ .table .tbody {
.tr.no-results{ .tr.no-results {
.td{ .td {
font-size: 15px; font-size: 15px;
padding: 26px 0; padding: 26px 0;
color: #5a5a77; color: #5a5a77;
@@ -58,11 +69,30 @@
} }
} }
.table{ .table {
.tr{
.th,
.td {
&.invoice_amount,
&.amount_due,
&.payment_amount {
&,
input {
text-align: right;
}
}
}
.tr {
.td:first-of-type, .td:first-of-type,
.th:first-of-type{ .th:first-of-type {
span, div{
span,
div {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
@@ -71,12 +101,12 @@
} }
} }
#{$self}__footer{ #{$self}__footer {
.form-group--statement{ .form-group--statement {
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
textarea{ textarea {
width: 100%; width: 100%;
min-height: 70px; min-height: 70px;
} }

View File

@@ -1,5 +1,14 @@
body.page-estimate-new,
body.page-estimate-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--estimate-form{ .dashboard__insider--estimate-form{
background-color: #FFF; padding-bottom: 64px;
} }
.page-form--estimate { .page-form--estimate {

View File

@@ -1,5 +1,14 @@
.dashboard__insider--invoice-form {
background-color: #fff; body.page-invoice-new,
body.page-invoice-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--invoice-form{
padding-bottom: 64px;
} }
.page-form--invoice { .page-form--invoice {
@@ -20,7 +29,7 @@
} }
.bp3-form-group { .bp3-form-group {
margin-bottom: 18px;
&.bp3-inline { &.bp3-inline {
max-width: 440px; max-width: 440px;

View File

@@ -1,5 +1,15 @@
body.page-receipt-new,
body.page-receipt-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--receipt-form{ .dashboard__insider--receipt-form{
background-color: #fff; padding-bottom: 64px;
} }
.page-form--receipt{ .page-form--receipt{

View File

@@ -1,5 +1,18 @@
@import '../../Base.scss'; @import '../../Base.scss';
body.page-vendor-new,
body.page-vendor-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--vendor-form{
padding-bottom: 64px;
}
.page-form--vendor { .page-form--vendor {
$self: '.page-form'; $self: '.page-form';
padding: 20px; padding: 20px;

View File

@@ -136,8 +136,8 @@ export default class BillsController extends BaseController {
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toFloat(), check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount').optional().isNumeric().toFloat(), check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
check('entries.*.description').optional().trim().escape(), check('entries.*.description').optional({ nullable: true }).trim().escape(),
]; ];
} }

View File

@@ -108,10 +108,10 @@ export default class SalesEstimatesController extends BaseController {
check('entries').exists().isArray({ min: 1 }), check('entries').exists().isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.description').optional().trim().escape(),
check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.discount').optional().isNumeric().toFloat(), check('entries.*.description').optional({ nullable: true }).trim().escape(),
check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
check('note').optional().trim().escape(), check('note').optional().trim().escape(),
check('terms_conditions').optional().trim().escape(), check('terms_conditions').optional().trim().escape(),

View File

@@ -106,8 +106,8 @@ export default class SaleInvoicesController extends BaseController {
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toFloat(), check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount').optional().isNumeric().toFloat(), check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
check('entries.*.description').optional().trim().escape(), check('entries.*.description').optional({ nullable: true }).trim().escape(),
]; ];
} }

View File

@@ -88,14 +88,14 @@ export default class SalesReceiptsController extends BaseController{
check('closed').default(false).isBoolean().toBoolean(), check('closed').default(false).isBoolean().toBoolean(),
check('entries').exists().isArray({ min: 1 }), check('entries').exists().isArray({ min: 1 }),
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(), check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.description').optional().trim().escape(),
check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toInt(), check('entries.*.rate').exists().isNumeric().toInt(),
check('entries.*.discount').optional().isNumeric().toInt(), check('entries.*.discount').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.description').optional({ nullable: true }).trim().escape(),
check('receipt_message').optional().trim().escape(), check('receipt_message').optional().trim().escape(),
check('statement').optional().trim().escape(), check('statement').optional().trim().escape(),