WIP feature/expenses

This commit is contained in:
elforjani3
2020-06-11 20:41:18 +02:00
parent 55a4319827
commit 45d9199dbb
27 changed files with 2761 additions and 616 deletions

View File

@@ -1,246 +1,246 @@
import React, { useState } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import {
FormGroup,
MenuItem,
Intent,
InputGroup,
Position,
Button,
TextArea,
ControlGroup
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { Select } from '@blueprintjs/select';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { momentFormatter } from 'utils';
import moment from 'moment';
import AppToaster from 'components/AppToaster';
// import React, { useState } from 'react';
// import * as Yup from 'yup';
// import { useFormik } from 'formik';
// import {
// FormGroup,
// MenuItem,
// Intent,
// InputGroup,
// Position,
// Button,
// TextArea,
// ControlGroup
// } from '@blueprintjs/core';
// import { DateInput } from '@blueprintjs/datetime';
// import { Select } from '@blueprintjs/select';
// import { FormattedMessage as T, useIntl } from 'react-intl';
// import { momentFormatter } from 'utils';
// import moment from 'moment';
// import AppToaster from 'components/AppToaster';
export default function ExpenseForm({
accounts,
editExpense,
submitExpense,
expenseDetails,
currencies
}) {
const {formatMessage} = useIntl();
// export default function ExpenseForm({
// accounts,
// editExpense,
// submitExpense,
// expenseDetails,
// currencies
// }) {
// const {formatMessage} = useIntl();
const [state, setState] = useState({
selectedExpenseAccount: null,
selectedPaymentAccount: null
});
const validationSchema = Yup.object().shape({
date: Yup.date().required().label(formatMessage({id:'date'})),
description: Yup.string().trim().label(formatMessage({id:'description'})),
expense_account_id: Yup.number().required().label(formatMessage({id:'expense_account_id'})),
payment_account_id: Yup.number().required().label(formatMessage({id:'payment_account_id'})),
amount: Yup.number().required().label(formatMessage({id:'amount'})),
currency_code: Yup.string().required().label(formatMessage({id:'currency_code_'})),
publish: Yup.boolean().label(formatMessage({id:'publish'})),
exchange_rate: Yup.number().label(formatMessage({id:'exchange_rate_'}))
});
// const [state, setState] = useState({
// selectedExpenseAccount: null,
// selectedPaymentAccount: null
// });
// const validationSchema = Yup.object().shape({
// date: Yup.date().required().label(formatMessage({id:'date'})),
// description: Yup.string().trim().label(formatMessage({id:'description'})),
// expense_account_id: Yup.number().required().label(formatMessage({id:'expense_account_id'})),
// payment_account_id: Yup.number().required().label(formatMessage({id:'payment_account_id'})),
// amount: Yup.number().required().label(formatMessage({id:'amount'})),
// currency_code: Yup.string().required().label(formatMessage({id:'currency_code_'})),
// publish: Yup.boolean().label(formatMessage({id:'publish'})),
// exchange_rate: Yup.number().label(formatMessage({id:'exchange_rate_'}))
// });
const formik = useFormik({
enableReinitialize: true,
validationSchema: validationSchema,
initialValues: {
date: null
},
onSubmit: values => {
submitExpense(values)
.then(response => {
AppToaster.show({
message: formatMessage({id:'the_expense_has_been_successfully_created'})
});
})
.catch(error => {});
}
});
// const formik = useFormik({
// enableReinitialize: true,
// validationSchema: validationSchema,
// initialValues: {
// date: null
// },
// onSubmit: values => {
// submitExpense(values)
// .then(response => {
// AppToaster.show({
// message: formatMessage({id:'the_expense_has_been_successfully_created'})
// });
// })
// .catch(error => {});
// }
// });
// Account item of select accounts field.
const accountItem = (item, { handleClick, modifiers, query }) => {
return (
<MenuItem
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick}
/>
);
};
// // Account item of select accounts field.
// const accountItem = (item, { handleClick, modifiers, query }) => {
// return (
// <MenuItem
// text={item.name}
// label={item.code}
// key={item.id}
// onClick={handleClick}
// />
// );
// };
const onChangeAccount = () => {};
// const onChangeAccount = () => {};
const onChangePaymentAccount = () => {};
// const onChangePaymentAccount = () => {};
const handleDateChange = date => {
const formatted = moment(date).format('YYYY/MM/DD');
formik.setFieldValue('date', formatted);
};
// const handleDateChange = date => {
// const formatted = moment(date).format('YYYY/MM/DD');
// formik.setFieldValue('date', formatted);
// };
// Filters accounts items.
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
const normalizedTitle = account.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
// // Filters accounts items.
// const filterAccountsPredicater = (query, account, _index, exactMatch) => {
// const normalizedTitle = account.name.toLowerCase();
// const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
// if (exactMatch) {
// return normalizedTitle === normalizedQuery;
// } else {
// return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
// }
// };
const onExpenseAccountSelect = account => {
setState({ ...state, selectedExpenseAccount: account });
formik.setFieldValue('expense_account_id', account.id);
};
// const onExpenseAccountSelect = account => {
// setState({ ...state, selectedExpenseAccount: account });
// formik.setFieldValue('expense_account_id', account.id);
// };
const onChangePaymentAccountSelect = account => {
setState({ ...state, selectedPaymentAccount: account });
formik.setFieldValue('payment_account_id', account.id);
};
// const onChangePaymentAccountSelect = account => {
// setState({ ...state, selectedPaymentAccount: account });
// formik.setFieldValue('payment_account_id', account.id);
// };
const onAmountCurrencySelect = currency => {
formik.setFieldValue('currency_code', currency.id);
};
// const onAmountCurrencySelect = currency => {
// formik.setFieldValue('currency_code', currency.id);
// };
const paymentAccountLabel = state.selectedPaymentAccount
? state.selectedPaymentAccount.name
: <T id={'select_payment_account'}/>;
// const paymentAccountLabel = state.selectedPaymentAccount
// ? state.selectedPaymentAccount.name
// : <T id={'select_payment_account'}/>;
const expenseAccountLabel = state.selectedExpenseAccount
? state.selectedExpenseAccount.name
: <T id={'select_expense_account'}/>;
// const expenseAccountLabel = state.selectedExpenseAccount
// ? state.selectedExpenseAccount.name
// : <T id={'select_expense_account'}/>;
const handleClose = () => {};
// const handleClose = () => {};
return (
<div class='expense-form'>
<form onSubmit={formik.handleSubmit}>
<FormGroup
label={<T id={'date'}/>}
inline={true}
intent={formik.errors.date && Intent.DANGER}
helperText={formik.errors.date && formik.errors.date}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
defaultValue={new Date()}
onChange={handleDateChange}
popoverProps={{ position: Position.BOTTOM }}
/>
</FormGroup>
// return (
// <div class='expense-form'>
// <form onSubmit={formik.handleSubmit}>
// <FormGroup
// label={<T id={'date'}/>}
// inline={true}
// intent={formik.errors.date && Intent.DANGER}
// helperText={formik.errors.date && formik.errors.date}
// >
// <DateInput
// {...momentFormatter('YYYY/MM/DD')}
// defaultValue={new Date()}
// onChange={handleDateChange}
// popoverProps={{ position: Position.BOTTOM }}
// />
// </FormGroup>
<FormGroup
label={<T id={'expense_account'}/>}
className={'form-group--expense-account'}
inline={true}
intent={formik.errors.expense_account_id && Intent.DANGER}
helperText={
formik.errors.expense_account_id && formik.errors.expense_account_id
}
>
<Select
items={accounts}
itemRenderer={accountItem}
itemPredicate={filterAccountsPredicater}
popoverProps={{ minimal: true }}
onItemSelect={onExpenseAccountSelect}
>
<Button
fill={true}
rightIcon='caret-down'
text={expenseAccountLabel}
/>
</Select>
</FormGroup>
// <FormGroup
// label={<T id={'expense_account'}/>}
// className={'form-group--expense-account'}
// inline={true}
// intent={formik.errors.expense_account_id && Intent.DANGER}
// helperText={
// formik.errors.expense_account_id && formik.errors.expense_account_id
// }
// >
// <Select
// items={accounts}
// itemRenderer={accountItem}
// itemPredicate={filterAccountsPredicater}
// popoverProps={{ minimal: true }}
// onItemSelect={onExpenseAccountSelect}
// >
// <Button
// fill={true}
// rightIcon='caret-down'
// text={expenseAccountLabel}
// />
// </Select>
// </FormGroup>
<FormGroup
label={<T id={'amount'}/>}
className={'form-group--amount'}
intent={formik.errors.amount && Intent.DANGER}
helperText={formik.errors.amount && formik.errors.amount}
inline={true}
>
<ControlGroup>
<Select
items={currencies.map(c => ({
id: c.currency_code,
name: c.currency_code
}))}
itemRenderer={accountItem}
itemPredicate={filterAccountsPredicater}
popoverProps={{ minimal: true }}
onItemSelect={onAmountCurrencySelect}
>
<Button
rightIcon='caret-down'
text={formik.values.currency_code}
/>
</Select>
// <FormGroup
// label={<T id={'amount'}/>}
// className={'form-group--amount'}
// intent={formik.errors.amount && Intent.DANGER}
// helperText={formik.errors.amount && formik.errors.amount}
// inline={true}
// >
// <ControlGroup>
// <Select
// items={currencies.map(c => ({
// id: c.currency_code,
// name: c.currency_code
// }))}
// itemRenderer={accountItem}
// itemPredicate={filterAccountsPredicater}
// popoverProps={{ minimal: true }}
// onItemSelect={onAmountCurrencySelect}
// >
// <Button
// rightIcon='caret-down'
// text={formik.values.currency_code}
// />
// </Select>
<InputGroup
medium={true}
intent={formik.errors.amount && Intent.DANGER}
{...formik.getFieldProps('amount')}
/>
</ControlGroup>
</FormGroup>
// <InputGroup
// medium={true}
// intent={formik.errors.amount && Intent.DANGER}
// {...formik.getFieldProps('amount')}
// />
// </ControlGroup>
// </FormGroup>
<FormGroup
label={<T id={'exchange_rate'}/>}
className={'form-group--exchange-rate'}
inline={true}
>
<InputGroup />
</FormGroup>
// <FormGroup
// label={<T id={'exchange_rate'}/>}
// className={'form-group--exchange-rate'}
// inline={true}
// >
// <InputGroup />
// </FormGroup>
<FormGroup
label={<T id={'payment_account'}/>}
className={'form-group--payment-account'}
inline={true}
intent={formik.errors.payment_account_id && Intent.DANGER}
helperText={
formik.errors.payment_account_id && formik.errors.payment_account_id
}
>
<Select
items={accounts}
itemRenderer={accountItem}
itemPredicate={filterAccountsPredicater}
popoverProps={{ minimal: true }}
onItemSelect={onChangePaymentAccountSelect}
>
<Button
fill={true}
rightIcon='caret-down'
text={paymentAccountLabel}
/>
</Select>
</FormGroup>
// <FormGroup
// label={<T id={'payment_account'}/>}
// className={'form-group--payment-account'}
// inline={true}
// intent={formik.errors.payment_account_id && Intent.DANGER}
// helperText={
// formik.errors.payment_account_id && formik.errors.payment_account_id
// }
// >
// <Select
// items={accounts}
// itemRenderer={accountItem}
// itemPredicate={filterAccountsPredicater}
// popoverProps={{ minimal: true }}
// onItemSelect={onChangePaymentAccountSelect}
// >
// <Button
// fill={true}
// rightIcon='caret-down'
// text={paymentAccountLabel}
// />
// </Select>
// </FormGroup>
<FormGroup
label={<T id={'description'}/>}
className={'form-group--description'}
inline={true}
>
<TextArea
growVertically={true}
large={true}
{...formik.getFieldProps('description')}
/>
</FormGroup>
// <FormGroup
// label={<T id={'description'}/>}
// className={'form-group--description'}
// inline={true}
// >
// <TextArea
// growVertically={true}
// large={true}
// {...formik.getFieldProps('description')}
// />
// </FormGroup>
<div class='form__floating-footer'>
<Button intent={Intent.PRIMARY} type='submit'>
<T id={'save'}/>
</Button>
<Button><T id={'save_as_draft'}/></Button>
<Button onClick={handleClose}><T id={'close'}/></Button>
</div>
</form>
</div>
);
}
// <div class='form__floating-footer'>
// <Button intent={Intent.PRIMARY} type='submit'>
// <T id={'save'}/>
// </Button>
// <Button><T id={'save_as_draft'}/></Button>
// <Button onClick={handleClose}><T id={'close'}/></Button>
// </div>
// </form>
// </div>
// );
// }

View File

@@ -1,82 +1,82 @@
import React from 'react';
import {
Button,
AnchorButton,
Classes,
NavbarGroup,
Popover,
MenuItem,
PopoverInteractionKind,
Position,
Menu,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import { useRouteMatch } from 'react-router-dom'
import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
// import React from 'react';
// import {
// Button,
// AnchorButton,
// Classes,
// NavbarGroup,
// Popover,
// MenuItem,
// PopoverInteractionKind,
// Position,
// Menu,
// NavbarDivider,
// Intent,
// } from '@blueprintjs/core';
// import { useRouteMatch } from 'react-router-dom'
// import { FormattedMessage as T } from 'react-intl';
// import classNames from 'classnames';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon';
// import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
// import Icon from 'components/Icon';
export default function ExpensesActionsBar() {
const {path} = useRouteMatch();
const onClickNewAccount = () => {};
const views = [];
// export default function ExpensesActionsBar() {
// const {path} = useRouteMatch();
// const onClickNewAccount = () => {};
// const views = [];
const viewsMenuItems = views.map((view) => {
return (<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />);
});
return (
<DashboardActionsBar>
<NavbarGroup>
<Popover
content={<Menu>{viewsMenuItems}</Menu>}
minimal={true}
interactionKind={PopoverInteractionKind.HOVER}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--table-views')}
icon={<Icon icon='table' />}
text={<T id={'table_views'}/>}
rightIcon={'caret-down'}
/>
</Popover>
// const viewsMenuItems = views.map((view) => {
// return (<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />);
// });
// return (
// <DashboardActionsBar>
// <NavbarGroup>
// <Popover
// content={<Menu>{viewsMenuItems}</Menu>}
// minimal={true}
// interactionKind={PopoverInteractionKind.HOVER}
// position={Position.BOTTOM_LEFT}
// >
// <Button
// className={classNames(Classes.MINIMAL, 'button--table-views')}
// icon={<Icon icon='table' />}
// text={<T id={'table_views'}/>}
// rightIcon={'caret-down'}
// />
// </Popover>
<NavbarDivider />
// <NavbarDivider />
<AnchorButton
className={Classes.MINIMAL}
icon={<Icon icon='plus' />}
href='/expenses/new'
text={<T id={'new_expense'}/>}
onClick={onClickNewAccount}
/>
<Button
className={Classes.MINIMAL}
intent={Intent.DANGER}
icon={<Icon icon='plus' />}
text={<T id={'delete'}/>}
onClick={onClickNewAccount}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon='plus' />}
text={<T id={'bulk_update'}/>}
onClick={onClickNewAccount}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon='file-import' />}
text={<T id={'import'}/>}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon='file-export' />}
text={<T id={'export'}/>}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
// <AnchorButton
// className={Classes.MINIMAL}
// icon={<Icon icon='plus' />}
// href='/expenses/new'
// text={<T id={'new_expense'}/>}
// onClick={onClickNewAccount}
// />
// <Button
// className={Classes.MINIMAL}
// intent={Intent.DANGER}
// icon={<Icon icon='plus' />}
// text={<T id={'delete'}/>}
// onClick={onClickNewAccount}
// />
// <Button
// className={Classes.MINIMAL}
// icon={<Icon icon='plus' />}
// text={<T id={'bulk_update'}/>}
// onClick={onClickNewAccount}
// />
// <Button
// className={Classes.MINIMAL}
// icon={<Icon icon='file-import' />}
// text={<T id={'import'}/>}
// />
// <Button
// className={Classes.MINIMAL}
// icon={<Icon icon='file-export' />}
// text={<T id={'export'}/>}
// />
// </NavbarGroup>
// </DashboardActionsBar>
// );
// }

View File

@@ -1,92 +1,92 @@
import React from 'react';
import {
GridComponent,
ColumnsDirective,
ColumnDirective,
Inject,
Sort,
// import React from 'react';
// import {
// GridComponent,
// ColumnsDirective,
// ColumnDirective,
// Inject,
// Sort,
} from '@syncfusion/ej2-react-grids';
import {
Checkbox,
Popover,
Button,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import Icon from 'components/Icon';
import moment from 'moment';
// } from '@syncfusion/ej2-react-grids';
// import {
// Checkbox,
// Popover,
// Button,
// Menu,
// MenuItem,
// MenuDivider,
// Position,
// } from '@blueprintjs/core';
// import Icon from 'components/Icon';
// import moment from 'moment';
export default function ExpensesTable({
expenses,
onDeleteExpense,
onEditExpense,
}) {
const onDateStateChange = () => {
// export default function ExpensesTable({
// expenses,
// onDeleteExpense,
// onEditExpense,
// }) {
// const onDateStateChange = () => {
}
// }
const actionMenuList = (expense) => (
<Menu>
<MenuItem text="View Details" />
<MenuDivider />
<MenuItem text="Edit Expense" onClick={() => onEditExpense(expense)} />
<MenuItem text="Delete Expense" onClick={() => onDeleteExpense(expense)} />
</Menu>
);
const columns = [
{
headerText: '',
template: () => (<Checkbox />)
},
{
headerText: 'Date',
template: (row) => (<span>{ moment(row.date).format('YYYY/MM/DD') }</span>),
},
{
headerText: 'Expense Account',
template: (row) => (<span>{ row.expenseAccount.name }</span>),
},
{
headerText: 'Paid Through',
template: (row) => (<span>{ row.paymentAccount.name }</span>),
},
{
headerText: 'Amount',
field: 'amount'
},
{
headerText: 'Status',
},
{
headerText: '',
template: (expense) => (
<Popover content={actionMenuList(expense)} position={Position.RIGHT_BOTTOM}>
<Button icon={<Icon icon="ellipsis-h" />} />
</Popover>
)
}
]
return (
<GridComponent
allowSorting={true}
dataSource={{ result: expenses, count: 20 }}
dataStateChange={onDateStateChange}>
// const actionMenuList = (expense) => (
// <Menu>
// <MenuItem text="View Details" />
// <MenuDivider />
// <MenuItem text="Edit Expense" onClick={() => onEditExpense(expense)} />
// <MenuItem text="Delete Expense" onClick={() => onDeleteExpense(expense)} />
// </Menu>
// );
// const columns = [
// {
// headerText: '',
// template: () => (<Checkbox />)
// },
// {
// headerText: 'Date',
// template: (row) => (<span>{ moment(row.date).format('YYYY/MM/DD') }</span>),
// },
// {
// headerText: 'Expense Account',
// template: (row) => (<span>{ row.expenseAccount.name }</span>),
// },
// {
// headerText: 'Paid Through',
// template: (row) => (<span>{ row.paymentAccount.name }</span>),
// },
// {
// headerText: 'Amount',
// field: 'amount'
// },
// {
// headerText: 'Status',
// },
// {
// headerText: '',
// template: (expense) => (
// <Popover content={actionMenuList(expense)} position={Position.RIGHT_BOTTOM}>
// <Button icon={<Icon icon="ellipsis-h" />} />
// </Popover>
// )
// }
// ]
// return (
// <GridComponent
// allowSorting={true}
// dataSource={{ result: expenses, count: 20 }}
// dataStateChange={onDateStateChange}>
<ColumnsDirective>
{columns.map((column) => {
return (<ColumnDirective
field={column.field}
headerText={column.headerText}
template={column.template}
allowSorting={true}
customAttributes={column.customAttributes}
/>);
})}
</ColumnsDirective>
<Inject services={[Sort]} />
</GridComponent>
);
}
// <ColumnsDirective>
// {columns.map((column) => {
// return (<ColumnDirective
// field={column.field}
// headerText={column.headerText}
// template={column.template}
// allowSorting={true}
// customAttributes={column.customAttributes}
// />);
// })}
// </ColumnsDirective>
// <Inject services={[Sort]} />
// </GridComponent>
// );
// }

View File

@@ -1,51 +1,51 @@
import React from 'react';
import {useHistory} from 'react-router';
import {connect} from 'react-redux';
import {
Alignment,
Navbar,
NavbarGroup,
Tabs,
Tab,
Button
} from "@blueprintjs/core";
import Icon from 'components/Icon';
import {useRouteMatch, Link} from 'react-router-dom';
// import React from 'react';
// import {useHistory} from 'react-router';
// import {connect} from 'react-redux';
// import {
// Alignment,
// Navbar,
// NavbarGroup,
// Tabs,
// Tab,
// Button
// } from "@blueprintjs/core";
// import Icon from 'components/Icon';
// import {useRouteMatch, Link} from 'react-router-dom';
function AccountsViewsTabs({ views }) {
const history = useHistory();
const {path} = useRouteMatch();
// function AccountsViewsTabs({ views }) {
// const history = useHistory();
// const {path} = useRouteMatch();
const handleClickNewView = () => {
history.push('/custom_views/new');
};
// const handleClickNewView = () => {
// history.push('/custom_views/new');
// };
const tabs = views.map((view) => {
const link = (<Link to={`${path}/${view.id}/custom_view`}>{ view.name }</Link>);
return (<Tab id={`custom_view_${view.id}`} title={link} />);
});
return (
<Navbar className="navbar--dashboard-views">
<NavbarGroup
align={Alignment.LEFT}>
<Tabs
id="navbar"
large={true}
className="tabs--dashboard-views"
>
{ tabs }
<Button
className="button--new-view"
icon={<Icon icon="plus" />}
onClick={handleClickNewView} />
</Tabs>
</NavbarGroup>
</Navbar>
);
}
// const tabs = views.map((view) => {
// const link = (<Link to={`${path}/${view.id}/custom_view`}>{ view.name }</Link>);
// return (<Tab id={`custom_view_${view.id}`} title={link} />);
// });
// return (
// <Navbar className="navbar--dashboard-views">
// <NavbarGroup
// align={Alignment.LEFT}>
// <Tabs
// id="navbar"
// large={true}
// className="tabs--dashboard-views"
// >
// { tabs }
// <Button
// className="button--new-view"
// icon={<Icon icon="plus" />}
// onClick={handleClickNewView} />
// </Tabs>
// </NavbarGroup>
// </Navbar>
// );
// }
const mapStateToProps = (state) => ({
views: state.views.resourceViews['expenses'],
});
// const mapStateToProps = (state) => ({
// views: state.views.resourceViews['expenses'],
// });
export default connect(mapStateToProps)(AccountsViewsTabs);
// export default connect(mapStateToProps)(AccountsViewsTabs);