mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
feat: style payment made form.
This commit is contained in:
@@ -9,12 +9,16 @@ const CLASSES = {
|
||||
PAGE_FORM_HEADER: 'page-form__header',
|
||||
PAGE_FORM_HEADER_PRIMARY: 'page-form__primary-section',
|
||||
PAGE_FORM_FOOTER: 'page-form__footer',
|
||||
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-action',
|
||||
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-actions',
|
||||
|
||||
PAGE_FORM_BILL: 'page-form--bill',
|
||||
PAGE_FORM_ESTIMATE: 'page-form--estimate',
|
||||
PAGE_FORM_INVOICE: 'page-form--invoice',
|
||||
PAGE_FORM_RECEIPT: 'page-form--receipt',
|
||||
PAGE_FORM_PAYMENT_MADE: 'page-form--payment-made',
|
||||
|
||||
CLOUD_SPINNER: 'cloud-spinner',
|
||||
IS_LOADING: 'is-loading',
|
||||
...Classes,
|
||||
};
|
||||
|
||||
|
||||
23
client/src/components/CloudLoadingIndicator.js
Normal file
23
client/src/components/CloudLoadingIndicator.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import If from './Utils/If';
|
||||
|
||||
export default function CloudLoadingIndicator({
|
||||
isLoading,
|
||||
children,
|
||||
}) {
|
||||
|
||||
return (
|
||||
<div className={classNames(
|
||||
CLASSES.CLOUD_SPINNER,
|
||||
{ [CLASSES.IS_LOADING]: isLoading },
|
||||
)}>
|
||||
<If condition={isLoading}>
|
||||
<Spinner size={30} value={null} />
|
||||
</If>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import InputPrependButton from './Forms/InputPrependButton';
|
||||
import CategoriesSelectList from './CategoriesSelectList';
|
||||
import Row from './Grid/Row';
|
||||
import Col from './Grid/Col';
|
||||
import CloudLoadingIndicator from './CloudLoadingIndicator';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
@@ -63,4 +64,5 @@ export {
|
||||
CategoriesSelectList,
|
||||
Col,
|
||||
Row,
|
||||
CloudLoadingIndicator,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Intent, Button } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
/**
|
||||
* Payment made floating actions bar.
|
||||
*/
|
||||
export default function PaymentMadeFloatingActions({
|
||||
isSubmitting,
|
||||
onSubmitClick,
|
||||
@@ -21,7 +27,7 @@ export default function PaymentMadeFloatingActions({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'estimate-form__floating-footer'}>
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { useMemo, useState, useCallback, useEffect } from 'react';
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import { useFormik } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick, sumBy } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
import PaymentMadeHeader from './PaymentMadeFormHeader';
|
||||
import PaymentMadeItemsTable from './PaymentMadeItemsTable';
|
||||
import PaymentMadeFloatingActions from './PaymentMadeFloatingActions';
|
||||
@@ -16,12 +18,10 @@ import withPaymentMadeDetail from './withPaymentMadeDetail';
|
||||
import withPaymentMade from './withPaymentMade';
|
||||
import { AppToaster } from 'components';
|
||||
|
||||
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
|
||||
import { compose, orderingLinesIndexes } from 'utils';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
const MIN_LINES_NUMBER = 5;
|
||||
|
||||
const ERRORS = {
|
||||
PAYMENT_NUMBER_NOT_UNIQUE: 'PAYMENT.NUMBER.NOT.UNIQUE',
|
||||
};
|
||||
@@ -70,8 +70,7 @@ function PaymentMadeForm({
|
||||
Yup.object().shape({
|
||||
id: Yup.number().nullable(),
|
||||
due_amount: Yup.number().nullable(),
|
||||
payment_amount: Yup.number().nullable()
|
||||
.max(Yup.ref("due_amount")),
|
||||
payment_amount: Yup.number().nullable().max(Yup.ref('due_amount')),
|
||||
bill_id: Yup.number()
|
||||
.nullable()
|
||||
.when(['payment_amount'], {
|
||||
@@ -124,7 +123,10 @@ function PaymentMadeForm({
|
||||
[paymentMade, defaultInitialValues, defaultPaymentMadeEntry],
|
||||
);
|
||||
|
||||
const handleSubmitForm = (values, { setSubmitting, resetForm, setFieldError }) => {
|
||||
const handleSubmitForm = (
|
||||
values,
|
||||
{ setSubmitting, resetForm, setFieldError },
|
||||
) => {
|
||||
setSubmitting(true);
|
||||
|
||||
// Filters entries that have no `bill_id` or `payment_amount`.
|
||||
@@ -165,7 +167,7 @@ function PaymentMadeForm({
|
||||
if (getError(ERRORS.PAYMENT_NUMBER_NOT_UNIQUE)) {
|
||||
setFieldError(
|
||||
'payment_number',
|
||||
formatMessage({ id: 'payment_number_is_not_unique' })
|
||||
formatMessage({ id: 'payment_number_is_not_unique' }),
|
||||
);
|
||||
}
|
||||
setSubmitting(false);
|
||||
@@ -251,7 +253,7 @@ function PaymentMadeForm({
|
||||
setClearFormAlert(true);
|
||||
}, []);
|
||||
|
||||
//
|
||||
//
|
||||
const handleCancelClearFormAlert = () => {
|
||||
setClearFormAlert(false);
|
||||
};
|
||||
@@ -270,7 +272,9 @@ function PaymentMadeForm({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'payment_made_form'}>
|
||||
<div
|
||||
className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_PAYMENT_MADE)}
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<PaymentMadeHeader
|
||||
paymentMadeId={paymentMadeId}
|
||||
@@ -282,6 +286,7 @@ function PaymentMadeForm({
|
||||
values={values}
|
||||
onFullAmountChanged={handleFullAmountChange}
|
||||
/>
|
||||
|
||||
<PaymentMadeItemsTable
|
||||
fullAmount={fullAmount}
|
||||
paymentEntries={values.entries}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
||||
import {
|
||||
AccountsSelectList,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
ErrorMessage,
|
||||
FieldRequiredHint,
|
||||
Money,
|
||||
Hint,
|
||||
} from 'components';
|
||||
|
||||
import withBills from '../Bill/withBills';
|
||||
@@ -118,8 +120,8 @@ function PaymentMadeFormHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
|
||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
||||
{/* ------------ Vendor name ------------ */}
|
||||
<FormGroup
|
||||
label={<T id={'vendor_name'} />}
|
||||
@@ -171,10 +173,10 @@ function PaymentMadeFormHeader({
|
||||
label={<T id={'full_amount'} />}
|
||||
inline={true}
|
||||
className={('form-group--full-amount', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={
|
||||
errors.full_amount && touched.full_amount && Intent.DANGER
|
||||
}
|
||||
labelInfo={<Hint />}
|
||||
helperText={
|
||||
<ErrorMessage name="full_amount" {...{ errors, touched }} />
|
||||
}
|
||||
@@ -189,7 +191,7 @@ function PaymentMadeFormHeader({
|
||||
onBlur={handleFullAmountBlur}
|
||||
/>
|
||||
|
||||
<a onClick={handleReceiveFullAmountClick} href="#">
|
||||
<a onClick={handleReceiveFullAmountClick} href="#" className={'receive-full-amount'}>
|
||||
Receive full amount (<Money amount={payableFullAmount} currency={'USD'} />)
|
||||
</a>
|
||||
</FormGroup>
|
||||
@@ -246,22 +248,22 @@ function PaymentMadeFormHeader({
|
||||
selectedAccountId={values.payment_account_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
{/* ------------ Reference ------------ */}
|
||||
<FormGroup
|
||||
label={<T id={'reference'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--reference', Classes.FILL)}
|
||||
intent={errors.reference && touched.reference && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
|
||||
>
|
||||
<InputGroup
|
||||
{/* ------------ Reference ------------ */}
|
||||
<FormGroup
|
||||
label={<T id={'reference'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--reference', Classes.FILL)}
|
||||
intent={errors.reference && touched.reference && Intent.DANGER}
|
||||
minimal={true}
|
||||
{...getFieldProps('reference')}
|
||||
/>
|
||||
</FormGroup>
|
||||
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
|
||||
>
|
||||
<InputGroup
|
||||
intent={errors.reference && touched.reference && Intent.DANGER}
|
||||
minimal={true}
|
||||
{...getFieldProps('reference')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { pick } from 'lodash';
|
||||
import { LoadingIndicator, Choose } from 'components';
|
||||
import { CloudLoadingIndicator } from 'components'
|
||||
import PaymentMadeItemsTableEditor from './PaymentMadeItemsTableEditor';
|
||||
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
@@ -73,7 +72,7 @@ function PaymentMadeItemsTable({
|
||||
setTableData(computedTableData);
|
||||
}, [computedTableData]);
|
||||
|
||||
// Handle
|
||||
// Handle mapping `fullAmount` prop to `localAmount` state.
|
||||
useEffect(() => {
|
||||
if (localAmount !== fullAmount) {
|
||||
let _fullAmount = fullAmount;
|
||||
@@ -111,23 +110,20 @@ function PaymentMadeItemsTable({
|
||||
triggerUpdateData(rows);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoadingIndicator loading={fetchVendorDueBills.isFetching}>
|
||||
<Choose>
|
||||
<Choose.When condition={tableData.length > 0}>
|
||||
<PaymentMadeItemsTableEditor
|
||||
data={tableData}
|
||||
errors={errors}
|
||||
onUpdateData={handleUpdateData}
|
||||
onClickClearAllLines={onClickClearAllLines}
|
||||
/>
|
||||
</Choose.When>
|
||||
const noResultsMessage = (vendorId) ?
|
||||
'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.';
|
||||
|
||||
<Choose.Otherwise>The vendor has no due invoices.</Choose.Otherwise>
|
||||
</Choose>
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
return (
|
||||
<CloudLoadingIndicator isLoading={fetchVendorDueBills.isFetching}>
|
||||
<PaymentMadeItemsTableEditor
|
||||
noResultsMessage={noResultsMessage}
|
||||
data={tableData}
|
||||
errors={errors}
|
||||
onUpdateData={handleUpdateData}
|
||||
onClickClearAllLines={onClickClearAllLines}
|
||||
/>
|
||||
</CloudLoadingIndicator>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ import { Button } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
import { sumBy } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { DataTable, Money } from 'components';
|
||||
import { transformUpdatedRows } from 'utils';
|
||||
import {
|
||||
@@ -37,13 +39,17 @@ export default function PaymentMadeItemsTableEditor({
|
||||
onClickClearAllLines,
|
||||
onUpdateData,
|
||||
data,
|
||||
errors
|
||||
errors,
|
||||
noResultsMessage
|
||||
}) {
|
||||
const transformedData = useMemo(() => {
|
||||
return [ ...data, {
|
||||
const rows = data;
|
||||
const totalRow = {
|
||||
due_amount: sumBy(data, 'due_amount'),
|
||||
payment_amount: sumBy(data, 'payment_amount'),
|
||||
}];
|
||||
};
|
||||
if (rows.length > 0) { rows.push(totalRow) }
|
||||
return rows;
|
||||
}, [data]);
|
||||
|
||||
const [localData, setLocalData] = useState(transformedData);
|
||||
@@ -71,10 +77,7 @@ export default function PaymentMadeItemsTableEditor({
|
||||
accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
|
||||
Cell: CellRenderer(EmptyDiv, 'bill_date'),
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 250,
|
||||
},
|
||||
|
||||
{
|
||||
Header: formatMessage({ id: 'bill_number' }),
|
||||
accessor: (row) => `#${row.bill_number}`,
|
||||
@@ -87,7 +90,6 @@ export default function PaymentMadeItemsTableEditor({
|
||||
accessor: 'amount',
|
||||
Cell: CellRenderer(DivFieldCell, 'amount'),
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
@@ -95,7 +97,6 @@ export default function PaymentMadeItemsTableEditor({
|
||||
accessor: 'due_amount',
|
||||
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
@@ -103,21 +104,19 @@ export default function PaymentMadeItemsTableEditor({
|
||||
accessor: 'payment_amount',
|
||||
Cell: TotalCellRederer(MoneyFieldCell, 'payment_amount'),
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
className: '',
|
||||
},
|
||||
],
|
||||
[formatMessage],
|
||||
);
|
||||
|
||||
// Handle click clear all lines button.
|
||||
const handleClickClearAllLines = () => {
|
||||
onClickClearAllLines && onClickClearAllLines();
|
||||
};
|
||||
|
||||
const rowClassNames = useCallback(
|
||||
(row) => {
|
||||
return { 'row--total': localData.length === row.index + 1 };
|
||||
},
|
||||
(row) => ({ 'row--total': localData.length === row.index + 1 }),
|
||||
[localData],
|
||||
);
|
||||
|
||||
@@ -137,7 +136,10 @@ export default function PaymentMadeItemsTableEditor({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={'estimate-form__table'}>
|
||||
<div className={classNames(
|
||||
CLASSES.DATATABLE_EDITOR,
|
||||
CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES,
|
||||
)}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={localData}
|
||||
@@ -147,11 +149,12 @@ export default function PaymentMadeItemsTableEditor({
|
||||
errors,
|
||||
updateData: handleUpdateData,
|
||||
}}
|
||||
noResults={noResultsMessage}
|
||||
/>
|
||||
<div className={'mt1'}>
|
||||
<div className={classNames(CLASSES.DATATABLE_EDITOR_ACTIONS, 'mt1')}>
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--clear-lines ml1'}
|
||||
className={'button--secondary button--clear-lines'}
|
||||
onClick={handleClickClearAllLines}
|
||||
>
|
||||
<T id={'clear_all_lines'} />
|
||||
|
||||
@@ -70,6 +70,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
@import 'pages/estimates';
|
||||
@import 'pages/invoice-form';
|
||||
@import 'pages/receipt-form';
|
||||
@import 'pages/payment-made';
|
||||
|
||||
// Views
|
||||
@import 'views/filter-dropdown';
|
||||
@@ -219,8 +220,16 @@ body.authentication {
|
||||
padding: 15px;
|
||||
margin: 15px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__floating-actions{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
padding: 14px 18px;
|
||||
border-top: 1px solid #ececec;
|
||||
}
|
||||
}
|
||||
.datatable-editor{
|
||||
|
||||
padding: 15px 15px 0;
|
||||
@@ -390,4 +399,33 @@ body.authentication {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.cloud-spinner{
|
||||
position: relative;
|
||||
|
||||
&.is-loading:before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.bp3-spinner{
|
||||
position: absolute;
|
||||
z-index: 999999;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-top: -20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
&:not(.is-loading) .bp3-spinner{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
61
client/src/style/pages/payment-made.scss
Normal file
61
client/src/style/pages/payment-made.scss
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
|
||||
.page-form--payment-made {
|
||||
$self: '.page-form';
|
||||
|
||||
#{$self}__header{
|
||||
.bp3-label{
|
||||
min-width: 160px;
|
||||
}
|
||||
.bp3-form-content{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bp3-form-group{
|
||||
margin-bottom: 18px;
|
||||
|
||||
&.bp3-inline{
|
||||
max-width: 470px;
|
||||
}
|
||||
a.receive-full-amount{
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#{$self}__primary-section{
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.datatable-editor{
|
||||
|
||||
.table .tbody{
|
||||
.tr.no-results{
|
||||
.td{
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
font-size: 15px;
|
||||
padding: 26px 0;
|
||||
color: #5a5a77;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table{
|
||||
.tr{
|
||||
.td:first-of-type,
|
||||
.th:first-of-type{
|
||||
span, div{
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#{$self}__footer{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user