feat: style bill form.

feat: add row and column of grid components.
This commit is contained in:
Ahmed Bouhuolia
2020-10-27 16:01:39 +02:00
parent 7558f68fa0
commit bb99a7694e
21 changed files with 631 additions and 246 deletions

View File

@@ -41,6 +41,7 @@
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css" type="text/css" >
<!-- <link href="https://cdn.syncfusion.com/ej2/material.css" rel="stylesheet"> --> <!-- <link href="https://cdn.syncfusion.com/ej2/material.css" rel="stylesheet"> -->
<!-- <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" /> --> <!-- <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" /> -->
</body> </body>

View File

@@ -1,6 +1,17 @@
import { Classes } from '@blueprintjs/core';
const CLASSES = { const CLASSES = {
DATATABLE_EDITOR: 'DATATABLE_EDITOR' DATATABLE_EDITOR: 'DATATABLE_EDITOR',
PAGE_FORM: 'page-form',
PAGE_FORM_HEADER: 'page-form__header',
PAGE_FORM_FOOTER: 'page-form__footer',
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-action',
PAGE_FORM_BILL: 'page-form--bill',
PAGE_FORM_ESTIMATE: 'page-form--estimate',
...Classes,
}; };
export { export {

View File

@@ -1,13 +1,12 @@
import React, { useCallback, useMemo, useEffect, useState } from 'react'; import React, { useCallback, useMemo, useEffect, useState } from 'react';
import { MenuItem } from '@blueprintjs/core'; import { MenuItem } from '@blueprintjs/core';
import ListSelect from 'components/ListSelect'; import ListSelect from 'components/ListSelect';
import { FormattedMessage as T } from 'react-intl';
function EstimateListField({ function EstimateListField({
products, products,
initialProductId, initialProductId,
selectedProductId, selectedProductId,
defautlSelectText = <T id={'select_product'} />, defautlSelectText = 'Click to select an item.',
onProductSelected, onProductSelected,
}) { }) {
const initialProduct = useMemo( const initialProduct = useMemo(

View File

@@ -0,0 +1,113 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const DEVICE_SIZES = ['xl', 'lg', 'md', 'sm', 'xs'];
const rowColWidth = PropTypes.oneOfType([PropTypes.number, PropTypes.string]);
const rowColumns = PropTypes.oneOfType([
rowColWidth,
PropTypes.shape({
cols: rowColWidth,
}),
]);
const propTypes = {
/**
* @default 'row'
*/
bsPrefix: PropTypes.string,
/** Removes the gutter spacing between `Col`s as well as any added negative margins. */
noGutters: PropTypes.bool.isRequired,
as: PropTypes.elementType,
/**
* The number of columns that will fit next to each other on extra small devices (<576px)
*
* @type {(number|{ cols: number })}
*/
xs: rowColumns,
/**
* The number of columns that will fit next to each other on small devices (≥576px)
*
* @type {(number|{ cols: number })}
*/
sm: rowColumns,
/**
* The number of columns that will fit next to each other on medium devices (≥768px)
*
* @type {(number|{ cols: number })}
*/
md: rowColumns,
/**
* The number of columns that will fit next to each other on large devices (≥992px)
*
* @type {(number|{ cols: number })}
*/
lg: rowColumns,
/**
* The number of columns that will fit next to each other on extra large devices (≥1200px)
*
* @type {(number|{ cols: number })}
*/
xl: rowColumns,
};
const defaultProps = {
noGutters: false,
};
function Row ({
bsPrefix,
className,
noGutters,
// Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
as: Component = 'div',
...props
}) {
const decoratedBsPrefix = '';
const sizePrefix = `col`;
const classes = [];
DEVICE_SIZES.forEach((brkPoint) => {
const propValue = props[brkPoint];
delete props[brkPoint];
let cols;
if (propValue != null && typeof propValue === 'object') {
({ cols } = propValue);
} else {
cols = propValue;
}
const infix = brkPoint !== 'xs' ? `-${brkPoint}` : '';
if (cols != null) classes.push(`${sizePrefix}${infix}-${cols}`);
});
return (
<Component
{...props}
className={classNames(
'col',
className,
decoratedBsPrefix,
noGutters && 'no-gutters',
...classes,
)}
/>
);
}
Row.displayName = 'Row';
Row.propTypes = propTypes;
Row.defaultProps = defaultProps;
export default Row;

View File

@@ -0,0 +1,111 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const DEVICE_SIZES = ['xl', 'lg', 'md', 'sm', 'xs'];
const rowColWidth = PropTypes.oneOfType([PropTypes.number, PropTypes.string]);
const rowColumns = PropTypes.oneOfType([
rowColWidth,
PropTypes.shape({
cols: rowColWidth,
}),
]);
const propTypes = {
/**
* @default 'row'
*/
bsPrefix: PropTypes.string,
/** Removes the gutter spacing between `Col`s as well as any added negative margins. */
noGutters: PropTypes.bool.isRequired,
as: PropTypes.elementType,
/**
* The number of columns that will fit next to each other on extra small devices (<576px)
*
* @type {(number|{ cols: number })}
*/
xs: rowColumns,
/**
* The number of columns that will fit next to each other on small devices (≥576px)
*
* @type {(number|{ cols: number })}
*/
sm: rowColumns,
/**
* The number of columns that will fit next to each other on medium devices (≥768px)
*
* @type {(number|{ cols: number })}
*/
md: rowColumns,
/**
* The number of columns that will fit next to each other on large devices (≥992px)
*
* @type {(number|{ cols: number })}
*/
lg: rowColumns,
/**
* The number of columns that will fit next to each other on extra large devices (≥1200px)
*
* @type {(number|{ cols: number })}
*/
xl: rowColumns,
};
const defaultProps = {
noGutters: false,
};
function Row ({
bsPrefix,
className,
noGutters,
// Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
as: Component = 'div',
...props
}) {
const decoratedBsPrefix = 'row';
const sizePrefix = `${decoratedBsPrefix}-cols`;
const classes = [];
DEVICE_SIZES.forEach((brkPoint) => {
const propValue = props[brkPoint];
delete props[brkPoint];
let cols;
if (propValue != null && typeof propValue === 'object') {
({ cols } = propValue);
} else {
cols = propValue;
}
const infix = brkPoint !== 'xs' ? `-${brkPoint}` : '';
if (cols != null) classes.push(`${sizePrefix}${infix}-${cols}`);
});
return (
<Component
{...props}
className={classNames(
className,
decoratedBsPrefix,
noGutters && 'no-gutters',
...classes,
)}
/>
);
}
Row.displayName = 'Row';
Row.propTypes = propTypes;
Row.defaultProps = defaultProps;
export default Row;

View File

@@ -26,6 +26,8 @@ import DialogContent from './Dialog/DialogContent';
import DialogSuspense from './Dialog/DialogSuspense'; import DialogSuspense from './Dialog/DialogSuspense';
import InputPrependButton from './Forms/InputPrependButton'; import InputPrependButton from './Forms/InputPrependButton';
import CategoriesSelectList from './CategoriesSelectList'; import CategoriesSelectList from './CategoriesSelectList';
import Row from './Grid/Row';
import Col from './Grid/Col';
const Hint = FieldHint; const Hint = FieldHint;
@@ -58,5 +60,7 @@ export {
DialogContent, DialogContent,
DialogSuspense, DialogSuspense,
InputPrependButton, InputPrependButton,
CategoriesSelectList CategoriesSelectList,
Col,
Row,
}; };

View File

@@ -456,8 +456,8 @@ export default compose(
withDashboardActions, withDashboardActions,
withMediaActions, withMediaActions,
withSettings(({ manualJournalsSettings }) => ({ withSettings(({ manualJournalsSettings }) => ({
journalNextNumber: manualJournalsSettings.next_number, journalNextNumber: manualJournalsSettings.nextNumber,
journalNumberPrefix: manualJournalsSettings.number_prefix journalNumberPrefix: manualJournalsSettings.numberPrefix
})), })),
withManualJournalsActions, withManualJournalsActions,
)(MakeJournalEntriesForm); )(MakeJournalEntriesForm);

View File

@@ -101,7 +101,7 @@ function AccountNameAccessor(row) {
<If condition={!row.active}> <If condition={!row.active}>
<InactiveSemafro /> <InactiveSemafro />
</If> </If>
</> </>
); );
} }
@@ -270,7 +270,7 @@ function AccountsDataTable({
); );
const selectionColumn = useMemo( const selectionColumn = useMemo(
() => ({ minWidth: 45, width: 45, maxWidth: 45 }), () => ({ minWidth: 40, width: 40, maxWidth: 40 }),
[], [],
); );

View File

@@ -342,10 +342,6 @@ function ExpenseForm({
category.amount && category.index && category.expense_account_id, category.amount && category.index && category.expense_account_id,
); );
console.log(categories, 'V');
console.log(formik.errors, 'Error');
return ( return (
<div className={'expense-form'}> <div className={'expense-form'}>
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>

View File

@@ -9,11 +9,10 @@ import * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { Intent, FormGroup, TextArea } from '@blueprintjs/core'; import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
import { Row, Col } from 'react-grid-system'; import classNames from 'classnames';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { CLASSES } from 'common/classes';
import BillFormHeader from './BillFormHeader'; import BillFormHeader from './BillFormHeader';
import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable'; import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable';
import BillFormFooter from './BillFormFooter'; import BillFormFooter from './BillFormFooter';
@@ -25,7 +24,7 @@ import withBillActions from './withBillActions';
import withBillDetail from './withBillDetail'; import withBillDetail from './withBillDetail';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import { AppToaster } from 'components'; import { AppToaster, Row, Col } from 'components';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import useMedia from 'hooks/useMedia'; import useMedia from 'hooks/useMedia';
@@ -306,34 +305,40 @@ function BillForm({
); );
}; };
return ( return (
<div className={'bill-form'}> <div className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_BILL,
)}>
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<BillFormHeader formik={formik} /> <BillFormHeader formik={formik} />
<EstimatesItemsTable <EstimatesItemsTable
formik={formik} formik={formik}
entries={formik.values.entries} entries={formik.values.entries}
onClickAddNewRow={onClickAddNewRow} onClickAddNewRow={onClickAddNewRow}
onClickClearAllLines={onClickCleanAllLines} onClickClearAllLines={onClickCleanAllLines}
/> />
<Row> <div class="page-form__footer">
<Col> <Row>
<FormGroup label={<T id={'note'} />} className={'form-group--'}> <Col md={8}>
<TextArea <FormGroup label={<T id={'note'} />} className={'form-group--note'}>
growVertically={true} <TextArea
{...formik.getFieldProps('note')} growVertically={true}
/> {...formik.getFieldProps('note')}
</FormGroup> />
</Col> </FormGroup>
</Col>
<Col> <Col md={4}>
<Dragzone <Dragzone
initialFiles={initialAttachmentFiles} initialFiles={initialAttachmentFiles}
onDrop={handleDropFiles} onDrop={handleDropFiles}
onDeleteFile={handleDeleteFile} onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</div>
</form> </form>
<BillFormFooter <BillFormFooter
formik={formik} formik={formik}

View File

@@ -1,24 +1,23 @@
import React, { useMemo, useCallback, useState } from 'react'; import React, { useCallback } from 'react';
import { import {
FormGroup, FormGroup,
InputGroup, InputGroup,
Intent, Intent,
Position, Position,
MenuItem, MenuItem,
Classes,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { Row, Col } from 'react-grid-system';
import moment from 'moment'; import moment from 'moment';
import { momentFormatter, compose, tansformDateValue } from 'utils'; import { momentFormatter, compose, tansformDateValue } from 'utils';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { import {
ListSelect, ListSelect,
ErrorMessage, ErrorMessage,
FieldRequiredHint, FieldRequiredHint,
Icon, Row,
InputPrependButton, Col,
} from 'components'; } from 'components';
// import withCustomers from 'containers/Customers/withCustomers'; // import withCustomers from 'containers/Customers/withCustomers';
@@ -30,13 +29,7 @@ function BillFormHeader({
formik: { errors, touched, setFieldValue, getFieldProps, values }, formik: { errors, touched, setFieldValue, getFieldProps, values },
//#withVendors //#withVendors
vendorsCurrentPage,
vendorItems, vendorItems,
//#withAccouts
accountsList,
// #withDialog
openDialog,
}) { }) {
const handleDateChange = useCallback( const handleDateChange = useCallback(
(date_filed) => (date) => { (date_filed) => (date) => {
@@ -80,18 +73,18 @@ function BillFormHeader({
} }
}; };
const handleBillNumberChange = useCallback(() => {
openDialog('bill-number-form', {});
}, [openDialog]);
return ( return (
<div className="page-form page-form--bill"> <div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<div className={'page-form__primary-section'}> <div className={'page-form__primary-section'}>
{/* Vendor account name */} {/* Vendor name */}
<FormGroup <FormGroup
label={<T id={'vendor_name'} />} label={<T id={'vendor_name'} />}
inline={true} inline={true}
className={classNames('form-group--select-list', Classes.FILL)} className={classNames(
'form-group--select-list',
'form-group--vendor',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={errors.vendor_id && touched.vendor_id && Intent.DANGER} intent={errors.vendor_id && touched.vendor_id && Intent.DANGER}
helperText={ helperText={
@@ -111,13 +104,15 @@ function BillFormHeader({
labelProp={'display_name'} labelProp={'display_name'}
/> />
</FormGroup> </FormGroup>
<Row>
<Col> <Row className={'row--bill-date'}>
<Col md={7}>
{/* Bill date */}
<FormGroup <FormGroup
label={<T id={'bill_date'} />} label={<T id={'bill_date'} />}
inline={true} inline={true}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', Classes.FILL)} className={classNames('form-group--select-list', CLASSES.FILL)}
intent={errors.bill_date && touched.bill_date && Intent.DANGER} intent={errors.bill_date && touched.bill_date && Intent.DANGER}
helperText={ helperText={
<ErrorMessage name="bill_date" {...{ errors, touched }} /> <ErrorMessage name="bill_date" {...{ errors, touched }} />
@@ -131,11 +126,17 @@ function BillFormHeader({
/> />
</FormGroup> </FormGroup>
</Col> </Col>
<Col>
<Col md={5}>
{/* Due date */}
<FormGroup <FormGroup
label={<T id={'due_date'} />} label={<T id={'due_date'} />}
inline={true} inline={true}
className={classNames('form-group--select-list', Classes.FILL)} className={classNames(
'form-group--due-date',
'form-group--select-list',
CLASSES.FILL
)}
intent={errors.due_date && touched.due_date && Intent.DANGER} intent={errors.due_date && touched.due_date && Intent.DANGER}
helperText={ helperText={
<ErrorMessage name="due_date" {...{ errors, touched }} /> <ErrorMessage name="due_date" {...{ errors, touched }} />
@@ -150,12 +151,12 @@ function BillFormHeader({
</FormGroup> </FormGroup>
</Col> </Col>
</Row> </Row>
{/* bill number */}
{/* Bill number */}
<FormGroup <FormGroup
label={<T id={'bill_number'} />} label={<T id={'bill_number'} />}
inline={true} inline={true}
className={('form-group--bill_number', Classes.FILL)} className={('form-group--bill_number', CLASSES.FILL)}
labelInfo={<FieldRequiredHint />}
intent={errors.bill_number && touched.bill_number && Intent.DANGER} intent={errors.bill_number && touched.bill_number && Intent.DANGER}
helperText={ helperText={
<ErrorMessage name="bill_number" {...{ errors, touched }} /> <ErrorMessage name="bill_number" {...{ errors, touched }} />
@@ -164,43 +165,35 @@ function BillFormHeader({
<InputGroup <InputGroup
intent={errors.bill_number && touched.bill_number && Intent.DANGER} intent={errors.bill_number && touched.bill_number && Intent.DANGER}
minimal={true} minimal={true}
rightElement={
<InputPrependButton
buttonProps={{
onClick: handleBillNumberChange,
icon: <Icon icon={'settings-18'} />,
}}
tooltip={true}
tooltipProps={{
content: 'Setting your auto-generated bill number',
position: Position.BOTTOM_LEFT,
}}
/>
}
{...getFieldProps('bill_number')} {...getFieldProps('bill_number')}
/> />
</FormGroup> </FormGroup>
</div>
<FormGroup {/* Reference */}
label={<T id={'reference'} />} <FormGroup
inline={true} label={<T id={'reference'} />}
className={classNames('form-group--reference', Classes.FILL)} inline={true}
intent={errors.reference_no && touched.reference_no && Intent.DANGER} className={classNames('form-group--reference', CLASSES.FILL)}
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
>
<InputGroup
intent={errors.reference_no && touched.reference_no && Intent.DANGER} intent={errors.reference_no && touched.reference_no && Intent.DANGER}
minimal={true} helperText={
{...getFieldProps('reference_no')} <ErrorMessage name="reference" {...{ errors, touched }} />
/> }
</FormGroup> >
<InputGroup
intent={
errors.reference_no && touched.reference_no && Intent.DANGER
}
minimal={true}
{...getFieldProps('reference_no')}
/>
</FormGroup>
</div>
</div> </div>
); );
} }
export default compose( export default compose(
withVendors(({ vendorsCurrentPage, vendorItems }) => ({ withVendors(({ vendorItems }) => ({
vendorsCurrentPage,
vendorItems, vendorItems,
})), })),
withAccounts(({ accountsList }) => ({ withAccounts(({ accountsList }) => ({

View File

@@ -4,8 +4,8 @@ import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { Hint, Icon } from 'components';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import Icon from 'components/Icon';
import { import {
InputGroupCell, InputGroupCell,
MoneyFieldCell, MoneyFieldCell,
@@ -66,6 +66,15 @@ const CellRenderer = (content, type) => (props) => {
return content(props); return content(props);
}; };
const ItemHeaderCell = () => {
return (
<>
<T id={'product_and_service'} />
<Hint />
</>
);
};
function EstimateTable({ function EstimateTable({
//#withitems //#withitems
itemsCurrentPage, itemsCurrentPage,
@@ -96,13 +105,12 @@ function EstimateTable({
className: 'index', className: 'index',
}, },
{ {
Header: formatMessage({ id: 'product_and_service' }), Header: ItemHeaderCell,
id: 'item_id', id: 'item_id',
accessor: 'item_id', accessor: 'item_id',
Cell: EstimatesListFieldCell, Cell: EstimatesListFieldCell,
disableSortBy: true, disableSortBy: true,
disableResizing: true, width: 180,
width: 250,
}, },
{ {
Header: formatMessage({ id: 'description' }), Header: formatMessage({ id: 'description' }),
@@ -110,7 +118,7 @@ function EstimateTable({
Cell: InputGroupCell, Cell: InputGroupCell,
disableSortBy: true, disableSortBy: true,
className: 'description', className: 'description',
width: 120, width: 100,
}, },
{ {
@@ -118,7 +126,7 @@ function EstimateTable({
accessor: 'quantity', accessor: 'quantity',
Cell: CellRenderer(InputGroupCell, 'quantity'), Cell: CellRenderer(InputGroupCell, 'quantity'),
disableSortBy: true, disableSortBy: true,
width: 100, width: 80,
className: 'quantity', className: 'quantity',
}, },
{ {
@@ -126,7 +134,7 @@ function EstimateTable({
accessor: 'rate', accessor: 'rate',
Cell: TotalEstimateCellRederer(MoneyFieldCell, 'rate'), Cell: TotalEstimateCellRederer(MoneyFieldCell, 'rate'),
disableSortBy: true, disableSortBy: true,
width: 100, width: 80,
className: 'rate', className: 'rate',
}, },
{ {
@@ -134,8 +142,7 @@ function EstimateTable({
accessor: 'discount', accessor: 'discount',
Cell: CellRenderer(PercentFieldCell, InputGroupCell), Cell: CellRenderer(PercentFieldCell, InputGroupCell),
disableSortBy: true, disableSortBy: true,
disableResizing: true, width: 80,
width: 100,
className: 'discount', className: 'discount',
}, },
{ {
@@ -144,7 +151,7 @@ function EstimateTable({
calculateDiscount(row.discount, row.quantity, row.rate), calculateDiscount(row.discount, row.quantity, row.rate),
Cell: TotalEstimateCellRederer(DivFieldCell, 'total'), Cell: TotalEstimateCellRederer(DivFieldCell, 'total'),
disableSortBy: true, disableSortBy: true,
width: 150, width: 120,
className: 'total', className: 'total',
}, },
{ {
@@ -222,7 +229,7 @@ function EstimateTable({
); );
return ( return (
<div className={'estimate-form__table'}> <div className={'estimate-form__table datatable-editor'}>
<DataTable <DataTable
columns={columns} columns={columns}
data={rows} data={rows}
@@ -235,7 +242,7 @@ function EstimateTable({
}} }}
className={CLASSES.DATATABLE_EDITOR} className={CLASSES.DATATABLE_EDITOR}
/> />
<div className={'datatable-editor-actions mt1'}> <div className={'datatable-editor__actions mt1'}>
<Button <Button
small={true} small={true}
className={'button--secondary button--new-line'} className={'button--secondary button--new-line'}

View File

@@ -11,8 +11,9 @@ import moment from 'moment';
import { Intent, FormGroup, TextArea } from '@blueprintjs/core'; import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { Row, Col } from 'react-grid-system'; import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import EstimateFormHeader from './EstimateFormHeader'; import EstimateFormHeader from './EstimateFormHeader';
import EstimatesItemsTable from './EntriesItemsTable'; import EstimatesItemsTable from './EntriesItemsTable';
import EstimateFormFooter from './EstimateFormFooter'; import EstimateFormFooter from './EstimateFormFooter';
@@ -23,7 +24,7 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withMediaActions from 'containers/Media/withMediaActions'; import withMediaActions from 'containers/Media/withMediaActions';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import AppToaster from 'components/AppToaster'; import { AppToaster, Row, Col } from 'components';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import useMedia from 'hooks/useMedia'; import useMedia from 'hooks/useMedia';
@@ -301,7 +302,10 @@ const EstimateForm = ({
}; };
return ( return (
<div> <div className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_ESTIMATE,
)}>
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<EstimateFormHeader formik={formik} /> <EstimateFormHeader formik={formik} />
<EstimatesItemsTable <EstimatesItemsTable
@@ -311,37 +315,39 @@ const EstimateForm = ({
formik={formik} formik={formik}
/> />
<Row> <div class={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Col> <Row>
<FormGroup <Col md={7}>
label={<T id={'customer_note'} />} <FormGroup
className={'form-group--customer_note'} label={<T id={'customer_note'} />}
> className={'form-group--customer_note'}
<TextArea >
growVertically={true} <TextArea
{...formik.getFieldProps('note')} growVertically={true}
{...formik.getFieldProps('note')}
/>
</FormGroup>
<FormGroup
label={<T id={'terms_conditions'} />}
className={'form-group--terms_conditions'}
>
<TextArea
growVertically={true}
{...formik.getFieldProps('terms_conditions')}
/>
</FormGroup>
</Col>
<Col md={5}>
<Dragzone
initialFiles={initialAttachmentFiles}
onDrop={handleDropFiles}
onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'}
/> />
</FormGroup> </Col>
<FormGroup </Row>
label={<T id={'terms_conditions'} />} </div>
className={'form-group--terms_conditions'}
>
<TextArea
growVertically={true}
{...formik.getFieldProps('terms_conditions')}
/>
</FormGroup>
</Col>
<Col>
<Dragzone
initialFiles={initialAttachmentFiles}
onDrop={handleDropFiles}
onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'}
/>
</Col>
</Row>
</form> </form>
<EstimateFormFooter <EstimateFormFooter
formik={formik} formik={formik}

View File

@@ -9,16 +9,18 @@ import {
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { Row, Col } from 'react-grid-system';
import moment from 'moment'; import moment from 'moment';
import { momentFormatter, compose, tansformDateValue } from 'utils'; import { momentFormatter, compose, tansformDateValue } from 'utils';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { import {
ListSelect, ListSelect,
ErrorMessage, ErrorMessage,
FieldRequiredHint, FieldRequiredHint,
Icon, Icon,
InputPrependButton, InputPrependButton,
Row,
Col,
} from 'components'; } from 'components';
import withCustomers from 'containers/Customers/withCustomers'; import withCustomers from 'containers/Customers/withCustomers';
@@ -81,7 +83,7 @@ function EstimateFormHeader({
}, [openDialog]); }, [openDialog]);
return ( return (
<div className={'page-form page-form--estimate'}> <div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<div className={'page-form__primary-section'}> <div className={'page-form__primary-section'}>
<FormGroup <FormGroup
label={<T id={'customer_name'} />} label={<T id={'customer_name'} />}
@@ -112,15 +114,15 @@ function EstimateFormHeader({
</FormGroup> </FormGroup>
<Row> <Row>
<Col> <Col md={8} className={'col--estimate-date'}>
<FormGroup <FormGroup
label={<T id={'estimate_date'} />} label={<T id={'estimate_date'} />}
inline={true} inline={true}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
className={classNames( className={classNames(
'form-group--select-list', 'form-group--select-list',
Classes.FILL,
'form-group--estimate-date', 'form-group--estimate-date',
Classes.FILL,
)} )}
intent={ intent={
errors.estimate_date && touched.estimate_date && Intent.DANGER errors.estimate_date && touched.estimate_date && Intent.DANGER
@@ -137,7 +139,7 @@ function EstimateFormHeader({
/> />
</FormGroup> </FormGroup>
</Col> </Col>
<Col> <Col md={4}>
<FormGroup <FormGroup
label={<T id={'expiration_date'} />} label={<T id={'expiration_date'} />}
inline={true} inline={true}
@@ -164,56 +166,58 @@ function EstimateFormHeader({
</FormGroup> </FormGroup>
</Col> </Col>
</Row> </Row>
</div>
{/*- Estimate -*/} {/*- Estimate -*/}
<FormGroup <FormGroup
label={<T id={'estimate'} />} label={<T id={'estimate'} />}
inline={true} inline={true}
className={('form-group--estimate-number', Classes.FILL)} className={('form-group--estimate-number', Classes.FILL)}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={
errors.estimate_number && touched.estimate_number && Intent.DANGER
}
helperText={
<ErrorMessage name="estimate_number" {...{ errors, touched }} />
}
>
<InputGroup
intent={ intent={
errors.estimate_number && touched.estimate_number && Intent.DANGER errors.estimate_number && touched.estimate_number && Intent.DANGER
} }
minimal={true} helperText={
rightElement={ <ErrorMessage name="estimate_number" {...{ errors, touched }} />
<InputPrependButton
buttonProps={{
onClick: handleEstimateNumberChange,
icon: <Icon icon={'settings-18'} />,
}}
tooltip={true}
tooltipProps={{
content: 'Setting your auto-generated estimate number',
position: Position.BOTTOM_LEFT,
}}
/>
} }
{...getFieldProps('estimate_number')} >
/> <InputGroup
</FormGroup> intent={
errors.estimate_number && touched.estimate_number && Intent.DANGER
}
minimal={true}
rightElement={
<InputPrependButton
buttonProps={{
onClick: handleEstimateNumberChange,
icon: <Icon icon={'settings-18'} />,
}}
tooltip={true}
tooltipProps={{
content: 'Setting your auto-generated estimate number',
position: Position.BOTTOM_LEFT,
}}
/>
}
{...getFieldProps('estimate_number')}
/>
</FormGroup>
<FormGroup <FormGroup
label={<T id={'reference'} />} label={<T id={'reference'} />}
inline={true} inline={true}
className={classNames('form-group--reference', Classes.FILL)} className={classNames('form-group--reference', Classes.FILL)}
intent={errors.reference && touched.reference && Intent.DANGER}
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
>
<InputGroup
intent={errors.reference && touched.reference && Intent.DANGER} intent={errors.reference && touched.reference && Intent.DANGER}
minimal={true} helperText={
{...getFieldProps('reference')} <ErrorMessage name="reference" {...{ errors, touched }} />
/> }
</FormGroup> >
<InputGroup
intent={errors.reference && touched.reference && Intent.DANGER}
minimal={true}
{...getFieldProps('reference')}
/>
</FormGroup>
</div>
</div> </div>
); );
} }

View File

@@ -94,65 +94,66 @@ function InvoiceFormHeader({
labelProp={'display_name'} labelProp={'display_name'}
/> />
</FormGroup> </FormGroup>
<Row>
<Col>
<FormGroup
label={<T id={'invoice_date'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', Classes.FILL)}
intent={
errors.invoice_date && touched.invoice_date && Intent.DANGER
}
helperText={
<ErrorMessage name="invoice_date" {...{ errors, touched }} />
}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(values.invoice_date)}
onChange={handleDateChange('invoice_date')}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
/>
</FormGroup>
</Col>
<Col>
<FormGroup
label={<T id={'due_date'} />}
inline={true}
className={classNames('form-group--select-list', Classes.FILL)}
intent={errors.due_date && touched.due_date && Intent.DANGER}
helperText={
<ErrorMessage name="due_date" {...{ errors, touched }} />
}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(values.due_date)}
onChange={handleDateChange('due_date')}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
/>
</FormGroup>
</Col>
</Row>
{/* invoice */}
<FormGroup
label={<T id={'invoice_no'} />}
inline={true}
className={('form-group--estimate', Classes.FILL)}
labelInfo={<FieldRequiredHint />}
intent={errors.invoice_no && touched.invoice_no && Intent.DANGER}
helperText={
<ErrorMessage name="invoice_no" {...{ errors, touched }} />
}
>
<InputGroup
intent={errors.invoice_no && touched.invoice_no && Intent.DANGER}
minimal={true}
{...getFieldProps('invoice_no')}
/>
</FormGroup>
</div> </div>
<Row>
<Col>
<FormGroup
label={<T id={'invoice_date'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', Classes.FILL)}
intent={
errors.invoice_date && touched.invoice_date && Intent.DANGER
}
helperText={
<ErrorMessage name="invoice_date" {...{ errors, touched }} />
}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(values.invoice_date)}
onChange={handleDateChange('invoice_date')}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
/>
</FormGroup>
</Col>
<Col>
<FormGroup
label={<T id={'due_date'} />}
inline={true}
className={classNames('form-group--select-list', Classes.FILL)}
intent={errors.due_date && touched.due_date && Intent.DANGER}
helperText={
<ErrorMessage name="due_date" {...{ errors, touched }} />
}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(values.due_date)}
onChange={handleDateChange('due_date')}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
/>
</FormGroup>
</Col>
</Row>
{/* invoice */}
<FormGroup
label={<T id={'invoice_no'} />}
inline={true}
className={('form-group--estimate', Classes.FILL)}
labelInfo={<FieldRequiredHint />}
intent={errors.invoice_no && touched.invoice_no && Intent.DANGER}
helperText={<ErrorMessage name="invoice_no" {...{ errors, touched }} />}
>
<InputGroup
intent={errors.invoice_no && touched.invoice_no && Intent.DANGER}
minimal={true}
{...getFieldProps('invoice_no')}
/>
</FormGroup>
<FormGroup <FormGroup
label={<T id={'reference'} />} label={<T id={'reference'} />}
inline={true} inline={true}

View File

@@ -4,10 +4,10 @@ export default (mapState) => {
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const mapped = { const mapped = {
organizationSettings: state.settings.data.organization, organizationSettings: state.settings.data.organization,
manualJournalsSettings: state.settings.data.manual_journals, manualJournalsSettings: state.settings.data.manualJournals,
billsettings: state.settings.data.bills, billsettings: state.settings.data.bills,
billPaymentSettings: state.settings.data.bill_payments, billPaymentSettings: state.settings.data.billPayments,
estimatesSettings: state.settings.data.sales_estimates, estimatesSettings: state.settings.data.salesEstimates,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -1,3 +1,4 @@
import { camelCase } from 'lodash';
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import t from 'store/types'; import t from 'store/types';
import { optionsArrayToMap } from 'utils'; import { optionsArrayToMap } from 'utils';
@@ -6,6 +7,10 @@ const initialState = {
organization: { organization: {
name: 'Bigcapital, Limited Liabilities', name: 'Bigcapital, Limited Liabilities',
}, },
manualJournals: {},
bills: {},
billPayments: {},
salesEstimates: {},
}, },
}; };
@@ -17,10 +22,13 @@ export default createReducer(initialState, {
}; };
options.forEach((option) => { options.forEach((option) => {
const { key, group, value } = option; const { key, group, value } = option;
if (!_data[group]) { const _group = camelCase(group);
_data[group] = {}; const _key = camelCase(key);
if (!_data[_group]) {
_data[_group] = {};
} }
_data[group][key] = value; _data[_group][_key] = value;
}); });
state.data = _data; state.data = _data;
}, },

View File

@@ -44,6 +44,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import 'components/pagination'; @import 'components/pagination';
@import 'components/resizer'; @import 'components/resizer';
// Pages
// Pages // Pages
@import 'pages/dashboard'; @import 'pages/dashboard';
@import 'pages/accounts-chart'; @import 'pages/accounts-chart';
@@ -65,6 +66,8 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import 'pages/billing'; @import 'pages/billing';
@import 'pages/register-wizard-page'; @import 'pages/register-wizard-page';
@import 'pages/register-organizaton'; @import 'pages/register-organizaton';
@import 'pages/bills';
@import 'pages/estimates';
// Views // Views
@import 'views/filter-dropdown'; @import 'views/filter-dropdown';
@@ -195,4 +198,53 @@ body.authentication {
opacity: 0.85; opacity: 0.85;
display: none; display: none;
} }
}
.page-form{
&__header{
padding: 20px;
}
&__primary-section{
background-color: #fbfbfb;
padding: 30px 20px 20px;
margin: -20px;
padding-bottom: 6px;
}
&__footer{
padding: 15px;
}
}
.datatable-editor{
.table{
.tbody{
.tr .td.actions .bp3-button{
background-color: transparent;
color: #e66d6d;
svg{
color: inherit;
}
}
}
}
&__actions{
.bp3-button{
padding-left: 10px;
padding-right: 10px;
}
.button--clear-lines{
&.bp3-button:not([class*="bp3-intent-"]):not(.bp3-minimal){
background-color: #fcefef;
}
}
}
} }

View File

@@ -0,0 +1,67 @@
.page-form--bill{
$self: '.page-form';
#{$self}__header{
.bp3-form-group{
margin-bottom: 18px;
}
.bp3-label{
min-width: 140px;
}
.bp3-form-content{
width: 100%;
}
.bp3-form-group{
&.bp3-inline{
max-width: 450px;
}
&.form-group{
&--vendor{
max-width: 650px
}
&--due-date{
max-width: 300px;
.bp3-label{
min-width: 95px;
}
}
&--expiration-date{
max-width: 340px;
.bp3-label{
min-width: 120px;
}
}
}
}
.row--bill-date{
.col:first-of-type{
max-width: 465px;
}
}
.col--estimate-date{
max-width: 530px;
}
}
#{$self}__footer{
.form-group--note{
max-width: 450px;
width: 100%;
textarea{
width: 100%;
min-height: 60px;
}
}
}
}

View File

@@ -0,0 +1,7 @@
.page-form--estimate{
$self: '.page-form';
#{$self}__header{
}
}

View File