This commit is contained in:
elforjani3
2021-03-08 16:20:01 +02:00
14 changed files with 220 additions and 213 deletions

View File

@@ -1,23 +1,21 @@
import React from 'react';
import { Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import { saveInvoke, removeRowsByIndex } from 'utils';
import { DataTableEditable } from 'components';
import withAlertActions from 'containers/Alert/withAlertActions';
import { updateDataReducer } from './utils';
import {
compose,
saveInvoke,
updateMinEntriesLines,
updateRemoveLineByIndex,
updateAutoAddNewLine,
updateTableRow,
} from 'utils';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import { useJournalTableEntriesColumns } from './components';
import { compose } from 'redux';
import { updateAdjustEntries } from './utils';
/**
* Make journal entries table component.
*/
function MakeJournalEntriesTable({
// #withAlertsActions
openAlert,
export default function MakeJournalEntriesTable({
// #ownPorps
onChange,
entries,
@@ -30,22 +28,34 @@ function MakeJournalEntriesTable({
// Memorized data table columns.
const columns = useJournalTableEntriesColumns();
// Handles update datatable data.
const handleUpdateData = (rowIndex, columnId, value) => {
const newRows = updateDataReducer(entries, rowIndex, columnId, value);
const newRows = compose(
// Auto-adding new lines.
updateAutoAddNewLine(defaultEntry, ['account_id', 'credit', 'debit']),
// Update items entries total.
updateAdjustEntries(rowIndex, columnId, value),
// Update entry of the given row index and column id.
updateTableRow(rowIndex, columnId, value),
)(entries);
saveInvoke(onChange, newRows);
};
// Handle remove datatable row.
const handleRemoveRow = (rowIndex) => {
const newRows = removeRowsByIndex(entries, rowIndex);
const newRows = compose(
// Ensure minimum lines count.
updateMinEntriesLines(minLinesNumber, defaultEntry),
// Remove the line by the given index.
updateRemoveLineByIndex(rowIndex),
)(entries);
saveInvoke(onChange, newRows);
};
return (
return (
<DataTableEditable
columns={columns}
data={entries}
@@ -63,8 +73,6 @@ function MakeJournalEntriesTable({
})),
autoFocus: ['account_id', 0],
}}
/>
/>
);
}
export default compose(withAlertActions)(MakeJournalEntriesTable);
}

View File

@@ -3,7 +3,7 @@ import { Intent } from '@blueprintjs/core';
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
import moment from 'moment';
import { transformUpdatedRows, repeatValue, transformToForm } from 'utils';
import { updateTableRow, repeatValue, transformToForm } from 'utils';
import { AppToaster } from 'components';
import { formatMessage } from 'services/intl';
@@ -70,10 +70,14 @@ function adjustmentEntries(entries) {
}
/**
*
* Adjustment credit/debit entries.
* @param {number} rowIndex
* @param {number} columnId
* @param {string} value
* @return {array}
*/
export const updateDataReducer = (rows, rowIndex, columnId, value) => {
let newRows = transformUpdatedRows(rows, rowIndex, columnId, value);
export const updateAdjustEntries = (rowIndex, columnId, value) => (rows) => {
let newRows = [...rows];
const oldCredit = get(rows, `[${rowIndex}].credit`);
const oldDebit = get(rows, `[${rowIndex}].debit`);
@@ -82,20 +86,10 @@ export const updateDataReducer = (rows, rowIndex, columnId, value) => {
const adjustment = adjustmentEntries(rows);
if (adjustment.credit) {
newRows = transformUpdatedRows(
newRows,
rowIndex,
'credit',
adjustment.credit,
);
newRows = updateTableRow(rowIndex, 'credit', adjustment.credit)(newRows);
}
if (adjustment.debit) {
newRows = transformUpdatedRows(
newRows,
rowIndex,
'debit',
adjustment.debit,
);
newRows = updateTableRow(rowIndex, 'debit', adjustment.debit)(newRows);
}
}
return newRows;

View File

@@ -17,6 +17,17 @@ export default function InviteUserFormContent() {
// Formik context.
const { isSubmitting } = useFormikContext();
const [passwordType, setPasswordType] = React.useState('password');
// Handle password revealer changing.
const handlePasswordRevealerChange = React.useCallback(
(shown) => {
const type = shown ? 'text' : 'password';
setPasswordType(type);
},
[setPasswordType],
);
return (
<Form>
<Row>
@@ -74,14 +85,14 @@ export default function InviteUserFormContent() {
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'password'} />}
labelInfo={<PasswordRevealer />}
labelInfo={<PasswordRevealer onChange={handlePasswordRevealerChange} />}
className={'form-group--password has-password-revealer'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'password'} />}
>
<InputGroup
lang={true}
// type={shown ? 'text' : 'password'}
type={passwordType}
intent={inputIntent({ error, touched })}
{...field}
/>

View File

@@ -6,7 +6,7 @@ import {
FormGroup,
Checkbox,
} from '@blueprintjs/core';
import { Form, ErrorMessage, FastField } from 'formik';
import { Form, ErrorMessage, Field } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { inputIntent } from 'utils';
import { PasswordRevealer } from './components';
@@ -14,12 +14,21 @@ import { PasswordRevealer } from './components';
/**
* Login form.
*/
export default function LoginForm({
isSubmitting
}) {
export default function LoginForm({ isSubmitting }) {
const [passwordType, setPasswordType] = React.useState('password');
// Handle password revealer changing.
const handlePasswordRevealerChange = React.useCallback(
(shown) => {
const type = shown ? 'text' : 'password';
setPasswordType(type);
},
[setPasswordType],
);
return (
<Form className={'authentication-page__form'}>
<FastField name={'crediential'}>
<Field name={'crediential'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'email_or_phone_number'} />}
@@ -34,13 +43,15 @@ export default function LoginForm({
/>
</FormGroup>
)}
</FastField>
</Field>
<FastField name={'password'}>
<Field name={'password'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'password'} />}
labelInfo={<PasswordRevealer />}
labelInfo={
<PasswordRevealer onChange={handlePasswordRevealerChange} />
}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'password'} />}
className={'form-group--password has-password-revealer'}
@@ -48,12 +59,12 @@ export default function LoginForm({
<InputGroup
large={true}
intent={inputIntent({ error, touched })}
// type={shown ? 'text' : 'password'}
type={passwordType}
{...field}
/>
</FormGroup>
)}
</FastField>
</Field>
<div className={'login-form__checkbox-section'}>
<Checkbox large={true} className={'checkbox--remember-me'}>

View File

@@ -4,26 +4,35 @@ import {
InputGroup,
Intent,
FormGroup,
Spinner
Spinner,
} from '@blueprintjs/core';
import { ErrorMessage, FastField, Form } from 'formik';
import { ErrorMessage, Field, Form } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { Link } from 'react-router-dom';
import { Row, Col, If } from 'components';
import { PasswordRevealer } from './components';
import { inputIntent } from 'utils';
/**
* Register form.
*/
export default function RegisterForm({
isSubmitting,
}) {
export default function RegisterForm({ isSubmitting }) {
const [passwordType, setPasswordType] = React.useState('password');
// Handle password revealer changing.
const handlePasswordRevealerChange = React.useCallback(
(shown) => {
const type = shown ? 'text' : 'password';
setPasswordType(type);
},
[setPasswordType],
);
return (
<Form className={'authentication-page__form'}>
<Row className={'name-section'}>
<Col md={6}>
<FastField name={'first_name'}>
<Field name={'first_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'first_name'} />}
@@ -37,11 +46,11 @@ export default function RegisterForm({
/>
</FormGroup>
)}
</FastField>
</Field>
</Col>
<Col md={6}>
<FastField name={'last_name'}>
<Field name={'last_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'last_name'} />}
@@ -55,11 +64,11 @@ export default function RegisterForm({
/>
</FormGroup>
)}
</FastField>
</Field>
</Col>
</Row>
<FastField name={'phone_number'}>
<Field name={'phone_number'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'phone_number'} />}
@@ -70,8 +79,9 @@ export default function RegisterForm({
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
<FastField name={'email'}>
</Field>
<Field name={'email'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'email'} />}
@@ -82,28 +92,28 @@ export default function RegisterForm({
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
</Field>
<FastField name={'password'}>
<Field name={'password'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'password'} />}
// labelInfo={passwordRevealerTmp}
intent={inputIntent({ error, touched })}
helperText={
<ErrorMessage name={'password'} />
labelInfo={
<PasswordRevealer onChange={handlePasswordRevealerChange} />
}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'password'} />}
className={'form-group--password has-password-revealer'}
>
<InputGroup
lang={true}
// type={shown ? 'text' : 'password'}
type={passwordType}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Field>
<div className={'register-form__agreement-section'}>
<p>

View File

@@ -2,10 +2,18 @@ import React from 'react';
import { FormattedMessage as T } from 'react-intl';
import ContentLoader from 'react-content-loader';
import { If, Icon } from 'components';
import { saveInvoke } from 'utils';
export function PasswordRevealer({ defaultShown = false, onChange }) {
const [shown, setShown] = React.useState(defaultShown);
const handleClick = () => {
setShown(!shown);
saveInvoke(onChange, !shown);
};
export function PasswordRevealer({ shown, onClick }) {
return (
<span class="password-revealer" onClick={onClick}>
<span class="password-revealer" onClick={handleClick}>
<If condition={shown}>
<Icon icon="eye-slash" />{' '}
<span class="text">

View File

@@ -1,40 +1,25 @@
import React, { useEffect, useCallback } from 'react';
import { Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
import { useItem } from 'hooks/query';
import ItemsEntriesDeleteAlert from 'containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert';
import withAlertActions from 'containers/Alert/withAlertActions';
import { CLASSES } from 'common/classes';
import { DataTableEditable } from 'components';
import { useEditableItemsEntriesColumns } from './components';
import {
saveInvoke,
updateTableRow,
repeatValue,
removeRowsByIndex,
compose,
updateTableRow,
updateMinEntriesLines,
updateAutoAddNewLine,
updateRemoveLineByIndex,
} 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.
*/
function ItemsEntriesTable({
// #withAlertActions
openAlert,
// #ownProps
items,
entries,
@@ -116,7 +101,7 @@ function ItemsEntriesTable({
setRowItem({ rowIndex, columnId, itemId: value });
}
const newRows = compose(
updateAutoAddNewLine(defaultEntry),
updateAutoAddNewLine(defaultEntry, ['item_id']),
updateItemsEntriesTotal,
updateTableRow(rowIndex, columnId, value),
)(rows);
@@ -129,55 +114,35 @@ function ItemsEntriesTable({
// Handle table rows removing by index.
const handleRemoveRow = (rowIndex) => {
const newRows = removeRowsByIndex(rows, rowIndex);
setRows(newRows);
saveInvoke(onUpdateData, newRows);
};
const newRows = compose(
// Ensure minimum lines count.
updateMinEntriesLines(4, defaultEntry),
// Remove the line by the given index.
updateRemoveLineByIndex(rowIndex),
)(rows);
// Handle table rows adding a new row.
const onClickNewRow = (event) => {
const newRows = [...rows, defaultEntry];
setRows(newRows);
saveInvoke(onUpdateData, newRows);
};
// Handle table clearing all rows.
const handleClickClearAllLines = (event) => {
openAlert('items-entries-clear-lines');
};
// Handle alert confirm of clear all lines.
const handleClearLinesAlertConfirm = () => {
const newRows = repeatValue(defaultEntry, linesNumber);
setRows(newRows);
saveInvoke(onUpdateData, newRows);
};
return (
<>
<DataTableEditable
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
columns={columns}
data={rows}
sticky={true}
progressBarLoading={isItemFetching}
cellsLoading={isItemFetching}
cellsLoadingCoords={cellsLoading}
footer={true}
payload={{
items,
errors: errors || [],
updateData: handleUpdateData,
removeRow: handleRemoveRow,
autoFocus: ['item_id', 0],
}}
/>
<ItemsEntriesDeleteAlert
name={'items-entries-clear-lines'}
onConfirm={handleClearLinesAlertConfirm}
/>
</>
<DataTableEditable
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
columns={columns}
data={rows}
sticky={true}
progressBarLoading={isItemFetching}
cellsLoading={isItemFetching}
cellsLoadingCoords={cellsLoading}
footer={true}
payload={{
items,
errors: errors || [],
updateData: handleUpdateData,
removeRow: handleRemoveRow,
autoFocus: ['item_id', 0],
}}
/>
);
}
@@ -186,7 +151,7 @@ ItemsEntriesTable.defaultProps = {
index: 0,
item_id: '',
description: '',
quantity: 1,
quantity: '',
rate: '',
discount: '',
},
@@ -194,4 +159,4 @@ ItemsEntriesTable.defaultProps = {
linesNumber: 4,
};
export default compose(withAlertActions)(ItemsEntriesTable);
export default ItemsEntriesTable;

View File

@@ -1,10 +1,11 @@
import { repeat } from 'lodash';
import { toSafeNumber } from 'utils';
/**
* Retrieve item entry total from the given rate, quantity and discount.
* @param {number} rate
* @param {number} quantity
* @param {number} discount
* @param {number} rate
* @param {number} quantity
* @param {number} discount
* @return {number}
*/
export const calcItemEntryTotal = (discount, quantity, rate) => {
@@ -21,9 +22,9 @@ export const calcItemEntryTotal = (discount, quantity, rate) => {
export function updateItemsEntriesTotal(rows) {
return rows.map((row) => ({
...row,
total: calcItemEntryTotal(row.discount, row.quantity, row.rate)
total: calcItemEntryTotal(row.discount, row.quantity, row.rate),
}));
};
}
export const ITEM_TYPE = {
SELLABLE: 'SELLABLE',

View File

@@ -3,12 +3,10 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import ExpenseFormEntriesField from './ExpenseFormEntriesField';
export default function ExpenseFormBody({
}) {
export default function ExpenseFormBody() {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<ExpenseFormEntriesField />
</div>
)
}
);
}

View File

@@ -1,7 +1,7 @@
import { FastField } from 'formik';
import React from 'react';
import ExpenseFormEntriesTable from './ExpenseFormEntriesTable';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import { defaultExpenseEntry } from './utils';
/**
* Expense form entries field.
@@ -9,8 +9,6 @@ import { useExpenseFormContext } from './ExpenseFormPageProvider';
export default function ExpenseFormEntriesField({
linesNumber = 4,
}) {
const { defaultCategoryEntry } = useExpenseFormContext();
return (
<FastField name={'categories'}>
{({ form, field: { value }, meta: { error, touched } }) => (
@@ -20,7 +18,7 @@ export default function ExpenseFormEntriesField({
onChange={(entries) => {
form.setFieldValue('categories', entries);
}}
defaultEntry={defaultCategoryEntry}
defaultEntry={defaultExpenseEntry}
linesNumber={linesNumber}
/>
)}

View File

@@ -1,23 +1,21 @@
import React, { useCallback } from 'react';
import { Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import { DataTableEditable } from 'components';
import ExpenseDeleteEntriesAlert from 'containers/Alerts/Expenses/ExpenseDeleteEntriesAlert';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import { useExpenseFormTableColumns } from './components';
import withAlertActions from 'containers/Alert/withAlertActions';
import { transformUpdatedRows, compose, saveInvoke, repeatValue } from 'utils';
import {
saveInvoke,
compose,
updateTableRow,
updateMinEntriesLines,
updateAutoAddNewLine,
updateRemoveLineByIndex,
} from 'utils';
/**
* Expenses form entries.
*/
function ExpenseFormEntriesTable({
// #withAlertActions
openAlert,
export default function ExpenseFormEntriesTable({
// #ownPorps
entries,
defaultEntry,
@@ -32,29 +30,30 @@ function ExpenseFormEntriesTable({
// Handles update datatable data.
const handleUpdateData = useCallback(
(rowIndex, columnIdOrObj, value) => {
const newRows = transformUpdatedRows(
entries,
rowIndex,
columnIdOrObj,
value,
);
(rowIndex, columnId, value) => {
const newRows = compose(
updateAutoAddNewLine(defaultEntry, ['expense_account_id']),
updateTableRow(rowIndex, columnId, value),
)(entries);
saveInvoke(onChange, newRows);
},
[entries, onChange],
[entries, defaultEntry, onChange],
);
// Handles click remove datatable row.
const handleRemoveRow = useCallback(
(rowIndex) => {
// Can't continue if there is just one row line or less.
if (entries.length <= 1) {
return;
}
const newRows = entries.filter((row, index) => index !== rowIndex);
const newRows = compose(
// Ensure minimum lines count.
updateMinEntriesLines(4, defaultEntry),
// Remove the line by the given index.
updateRemoveLineByIndex(rowIndex),
)(entries);
saveInvoke(onChange, newRows);
},
[entries, onChange],
[entries, defaultEntry, onChange],
);
return (
@@ -72,6 +71,4 @@ function ExpenseFormEntriesTable({
footer={true}
/>
);
}
export default compose(withAlertActions)(ExpenseFormEntriesTable);
}

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React from 'react';
import { FastField } from 'formik';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';