Merge branch 'develop' into big-44-auto-re-calculate-the-items-rate-once-changing-the-invoice

This commit is contained in:
Ahmed Bouhuolia
2024-01-11 20:27:42 +02:00
403 changed files with 24846 additions and 9622 deletions

View File

@@ -51,8 +51,8 @@ export default function Dashboard() {
</Switch>
<DashboardUniversalSearch />
<DialogsContainer />
<GlobalHotkeys />
<DialogsContainer />
<DrawersContainer />
<AlertsContainer />
</DashboardProvider>

View File

@@ -46,6 +46,10 @@ import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/P
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
import { DialogsName } from '@/constants/dialogs';
import InvoiceExchangeRateChangeDialog from '@/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog';
import InvoiceMailDialog from '@/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog';
import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog';
import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog';
import PaymentMailDialog from '@/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog';
/**
* Dialogs container.
@@ -138,6 +142,10 @@ export default function DialogsContainer() {
<InvoiceExchangeRateChangeDialog
dialogName={DialogsName.InvoiceExchangeRateChangeNotice}
/>
<InvoiceMailDialog dialogName={DialogsName.InvoiceMail} />
<EstimateMailDialog dialogName={DialogsName.EstimateMail} />
<ReceiptMailDialog dialogName={DialogsName.ReceiptMail} />
<PaymentMailDialog dialogName={DialogsName.PaymentMail} />
</div>
);
}

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { FieldConfig, FieldProps } from 'formik';
import { Field } from '@blueprintjs-formik/core';
import { RichEditor, RichEditorProps } from '../../components/RichEditor';
export interface FRichEditorProps
extends Omit<FieldConfig, 'children' | 'component' | 'as'>,
RichEditorProps {
name: string;
value?: string;
}
interface FieldToRichEditorProps
extends FieldProps,
Omit<RichEditorProps, 'form'> {}
/**
* Transformes the field props to `RichEditor` props.
* @param {FieldToRichEditorProps}
* @returns {HTMLSelectProps}
*/
function fieldToRichEditor({
field: { onBlur: onFieldBlur, ...field },
form: { touched, errors, ...form },
...props
}: FieldToRichEditorProps): RichEditorProps {
return {
...field,
...props,
onChange: (value: string) => {
form.setFieldValue(field.name, value);
},
};
}
/**
* Transformes field props to `RichEditor` props.
* @param {FieldToRichEditorProps}
* @returns {JSX.Element}
*/
function FieldToRichEditor({ ...props }: FieldToRichEditorProps): JSX.Element {
return <RichEditor {...fieldToRichEditor(props)} />;
}
/**
* Rich editor wrapper to bind with Formik.
* @param {FRichEditorProps} props -
* @returns {JSX.Element}
*/
export function FRichEditor({ ...props }: FRichEditorProps): JSX.Element {
return <Field {...props} component={FieldToRichEditor} />;
}

View File

@@ -4,4 +4,5 @@ export * from './FMoneyInputGroup';
export * from './BlueprintFormik';
export * from './InputPrependText';
export * from './InputPrependButton';
export * from './MoneyInputGroup';
export * from './MoneyInputGroup';
export * from './FRichEditor';

View File

@@ -0,0 +1,66 @@
/* Basic editor styles */
.tiptap {
color: #222;
&:focus-visible {
outline: none;
}
>*+* {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
code {
background: rgba(#ffffff, 0.1);
color: rgba(#ffffff, 0.6);
border: 1px solid rgba(#ffffff, 0.1);
border-radius: 0.5rem;
padding: 0.2rem;
}
pre {
background: rgba(#ffffff, 0.1);
font-family: "JetBrainsMono", monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
border: none;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
margin-left: 0;
padding-left: 1rem;
border-left: 2px solid rgba(#ffffff, 0.4);
hr {
border: none;
border-top: 2px solid rgba(#ffffff, 0.1);
margin: 2rem 0;
}
}
}

View File

@@ -0,0 +1,58 @@
// @ts-nocheck
import { Color } from '@tiptap/extension-color';
import ListItem from '@tiptap/extension-list-item';
import TextStyle from '@tiptap/extension-text-style';
import { EditorProvider } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { useUncontrolled } from '@/hooks/useUncontrolled';
import { Box } from '../Layout/Box';
import './RichEditor.style.scss';
const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false,
},
orderedList: {
keepMarks: true,
keepAttributes: false,
},
}),
];
export interface RichEditorProps {
value?: string;
initialValue?: string;
onChange?: (value: string) => void;
className?: string;
}
export const RichEditor = ({
value,
initialValue,
onChange,
className,
}: RichEditorProps) => {
const [content, handleChange] = useUncontrolled({
value,
initialValue,
onChange,
finalValue: '',
});
const handleBlur = ({ editor }) => {
handleChange(editor.getHTML());
};
return (
<Box className={className}>
<EditorProvider
extensions={extensions}
content={content}
onBlur={handleBlur}
/>
</Box>
);
};

View File

@@ -0,0 +1 @@
export * from './RichEditor';

View File

@@ -48,5 +48,9 @@ export enum DialogsName {
ProjectBillableEntriesForm = 'project-billable-entries',
InvoiceNumberSettings = 'InvoiceNumberSettings',
TaxRateForm = 'tax-rate-form',
InvoiceExchangeRateChangeNotice = 'InvoiceExchangeRateChangeNotice'
InvoiceExchangeRateChangeNotice = 'InvoiceExchangeRateChangeNotice',
InvoiceMail = 'invoice-mail',
EstimateMail = 'estimate-mail',
ReceiptMail = 'receipt-mail',
PaymentMail = 'payment-mail',
}

View File

@@ -12,6 +12,22 @@ export default [
text: <T id={'users'} />,
href: '/preferences/users',
},
{
text: <T id={'preferences.estimates'} />,
href: '/preferences/estimates',
},
{
text: <T id={'preferences.invoices'} />,
href: '/preferences/invoices',
},
{
text: <T id={'preferences.receipts'} />,
href: '/preferences/receipts',
},
{
text: <T id={'preferences.creditNotes'} />,
href: '/preferences/credit-notes',
},
{
text: <T id={'currencies'} />,
href: '/preferences/currencies',

View File

@@ -32,7 +32,7 @@ function AccountsDataTable({
// #withAlertsDialog
openAlert,
// #withDial
// #withDialog
openDialog,
// #withDrawerActions

View File

@@ -8,9 +8,9 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group';
import authenticationRoutes from '@/routes/authentication';
import { Icon, FormattedMessage as T } from '@/components';
import { useIsAuthenticated } from '@/hooks/state';
import { AuthMetaBootProvider } from './AuthMetaBoot';
import '@/style/pages/Authentication/Auth.scss';
import { AuthMetaBootProvider } from './AuthMetaBoot';
export function Authentication() {
const to = { pathname: '/' };

View File

@@ -29,7 +29,7 @@ const Schema = Yup.object().shape({
billing_address_2: Yup.string().trim(),
billing_address_city: Yup.string().trim(),
billing_address_state: Yup.string().trim(),
billing_address_postcode: Yup.number().nullable(),
billing_address_postcode: Yup.string().nullable(),
billing_address_phone: Yup.number(),
shipping_address_country: Yup.string().trim(),
@@ -37,7 +37,7 @@ const Schema = Yup.object().shape({
shipping_address_2: Yup.string().trim(),
shipping_address_city: Yup.string().trim(),
shipping_address_state: Yup.string().trim(),
shipping_address_postcode: Yup.number().nullable(),
shipping_address_postcode: Yup.string().nullable(),
shipping_address_phone: Yup.number(),
opening_balance: Yup.number().nullable(),

View File

@@ -63,8 +63,7 @@ export const useBillReadonlyEntriesTableColumns = () => {
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
accessor: 'rate_formatted',
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
@@ -75,9 +74,8 @@ export const useBillReadonlyEntriesTableColumns = () => {
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
accessor: 'total_formatted',
width: getColumnWidth(entries, 'total_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -60,9 +60,8 @@ export const useCreditNoteReadOnlyEntriesColumns = () => {
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
accessor: 'rate_formatted',
width: getColumnWidth(entries, 'rate_formatted', {
minWidth: 60,
magicSpacing: 5,
}),
@@ -72,9 +71,8 @@ export const useCreditNoteReadOnlyEntriesColumns = () => {
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
accessor: 'total_formatted',
width: getColumnWidth(entries, 'total_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -1,5 +1,4 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
@@ -22,10 +21,12 @@ import {
Icon,
FormattedMessage as T,
Can,
Choose,
} from '@/components';
import { compose } from '@/utils';
import { DRAWERS } from '@/constants/drawers';
import { DialogsName } from '@/constants/dialogs';
/**
* Estimate read-only details actions bar of the drawer.
@@ -51,7 +52,6 @@ function EstimateDetailActionsBar({
history.push(`/estimates/${estimateId}/edit`);
closeDrawer(DRAWERS.ESTIMATE_DETAILS);
};
// Handle delete sale estimate.
const handleDeleteEstimate = () => {
openAlert('estimate-delete', { estimateId });
@@ -65,6 +65,10 @@ function EstimateDetailActionsBar({
const handleNotifyViaSMS = () => {
openDialog('notify-estimate-via-sms', { estimateId });
};
// Handles the estimate mail dialog.
const handleMailEstimate = () => {
openDialog(DialogsName.EstimateMail, { estimateId });
};
return (
<DrawerActionsBar>
@@ -78,13 +82,21 @@ function EstimateDetailActionsBar({
/>
<NavbarDivider />
</Can>
<Can I={SaleEstimateAction.View} a={AbilitySubject.Estimate}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="envelope" />}
text={'Send Mail'}
onClick={handleMailEstimate}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintEstimate}
/>
<NavbarDivider />
</Can>
<Can I={SaleEstimateAction.Delete} a={AbilitySubject.Estimate}>
<Button

View File

@@ -1,5 +1,4 @@
// @ts-nocheck
import React from 'react';
import {
Intent,
Button,
@@ -9,9 +8,15 @@ import {
MenuItem,
Menu,
Tag,
MenuDivider,
Classes,
} from '@blueprintjs/core';
import * as R from 'ramda';
import { Icon, T, Choose } from '@/components';
import { Icon, T, Choose, Can } from '@/components';
import { AbilitySubject, SaleEstimateAction } from '@/constants/abilityOption';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
/**
* Estimate details status.
@@ -49,25 +54,85 @@ export function EstimateDetailsStatus({ estimate }) {
);
}
export function EstimateMoreMenuItems({ payload: { onNotifyViaSMS } }) {
return (
<Popover
minimal={true}
content={
<Menu>
<MenuItem
onClick={onNotifyViaSMS}
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
/>
</Menu>
}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
>
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
</Popover>
);
}
export const EstimateMoreMenuItems = R.compose(withAlertsActions)(
({
// # withAlertsActions,
openAlert,
// # rest
payload: { onNotifyViaSMS },
}) => {
const { estimateId, estimate } = useEstimateDetailDrawerContext();
// Handle cancel/confirm estimate approve.
const handleApproveEstimate = () => {
openAlert('estimate-Approve', { estimateId });
};
// Handle cancel/confirm estimate reject.
const handleRejectEstimate = () => {
openAlert('estimate-reject', { estimateId });
};
return (
<Popover
minimal={true}
content={
<Menu>
<MenuItem
onClick={onNotifyViaSMS}
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
/>
<MenuDivider />
<Choose>
<Choose.When
condition={estimate.is_delivered && estimate.is_rejected}
>
<Can I={SaleEstimateAction.Edit} a={AbilitySubject.Estimate}>
<MenuItem
className={Classes.MINIMAL}
text={<T id={'mark_as_approved'} />}
onClick={handleApproveEstimate}
/>
</Can>
</Choose.When>
<Choose.When
condition={estimate.is_delivered && estimate.is_approved}
>
<Can I={SaleEstimateAction.Edit} a={AbilitySubject.Estimate}>
<MenuItem
className={Classes.MINIMAL}
text={<T id={'mark_as_rejected'} />}
onClick={handleRejectEstimate}
/>
</Can>
</Choose.When>
<Choose.When condition={estimate.is_delivered}>
<Can I={SaleEstimateAction.Edit} a={AbilitySubject.Estimate}>
<MenuItem
className={Classes.MINIMAL}
text={<T id={'mark_as_approved'} />}
onClick={handleApproveEstimate}
/>
</Can>
<Can I={SaleEstimateAction.Edit} a={AbilitySubject.Estimate}>
<MenuItem
className={Classes.MINIMAL}
text={<T id={'mark_as_rejected'} />}
onClick={handleRejectEstimate}
/>
</Can>
</Choose.When>
</Choose>
</Menu>
}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
>
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
</Popover>
);
},
);

View File

@@ -47,9 +47,8 @@ export const useEstimateReadonlyEntriesColumns = () => {
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
accessor: 'rate_formatted',
width: getColumnWidth(entries, 'rate_formatted', {
minWidth: 60,
magicSpacing: 5,
}),
@@ -59,9 +58,9 @@ export const useEstimateReadonlyEntriesColumns = () => {
},
{
Header: intl.get('amount'),
accessor: 'amount',
accessor: 'total_formatted',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
width: getColumnWidth(entries, 'total_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -31,6 +31,7 @@ import {
import { compose } from '@/utils';
import { BadDebtMenuItem } from './utils';
import { DRAWERS } from '@/constants/drawers';
import { DialogsName } from '@/constants/dialogs';
/**
* Invoice details action bar.
@@ -93,6 +94,10 @@ function InvoiceDetailActionsBar({
openAlert('cancel-bad-debt', { invoiceId });
};
const handleMailInvoice = () => {
openDialog(DialogsName.InvoiceMail, { invoiceId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
@@ -117,12 +122,19 @@ function InvoiceDetailActionsBar({
<NavbarDivider />
</Can>
<Can I={SaleInvoiceAction.View} a={AbilitySubject.Invoice}>
<Button
text={'Send Mail'}
icon={<Icon icon="envelope" />}
onClick={handleMailInvoice}
className={Classes.MINIMAL}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintInvoice}
/>
<NavbarDivider />
</Can>
<Can I={SaleInvoiceAction.Delete} a={AbilitySubject.Invoice}>
<Button

View File

@@ -23,7 +23,7 @@ export function InvoiceDetailTableFooter() {
<InvoiceTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'invoice.details.subtotal'} />}
value={<FormatNumber value={invoice.subtotal_formatted} />}
value={invoice.subtotal_formatted}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
{invoice.taxes.map((taxRate) => (

View File

@@ -64,24 +64,22 @@ export const useInvoiceReadonlyEntriesColumns = () => {
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
accessor: 'rate_formatted',
align: 'right',
disableSortBy: true,
textOverview: true,
width: getColumnWidth(entries, 'rate', {
width: getColumnWidth(entries, 'rate_formatted', {
minWidth: 60,
magicSpacing: 5,
}),
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
accessor: 'total_formatted',
align: 'right',
disableSortBy: true,
textOverview: true,
width: getColumnWidth(entries, 'amount', {
width: getColumnWidth(entries, 'total_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -52,9 +52,8 @@ export const usePaymentMadeEntriesColumns = () => {
},
{
Header: intl.get('payment_amount'),
accessor: 'payment_amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'payment_amount', {
accessor: 'payment_amount_formatted',
width: getColumnWidth(entries, 'payment_amount_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -28,6 +28,7 @@ import {
import { compose } from '@/utils';
import { DRAWERS } from '@/constants/drawers';
import { DialogsName } from '@/constants/dialogs';
/**
* Payment receive actions bar.
@@ -68,6 +69,10 @@ function PaymentReceiveActionsBar({
openDialog('payment-pdf-preview', { paymentReceiveId });
};
const handleMailPaymentReceive = () => {
openDialog(DialogsName.PaymentMail, { paymentReceiveId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
@@ -81,12 +86,19 @@ function PaymentReceiveActionsBar({
<NavbarDivider />
</Can>
<Can I={PaymentReceiveAction.View} a={AbilitySubject.PaymentReceive}>
<Button
className={Classes.MINIMAL}
text={'Send Mail'}
icon={<Icon icon="envelope" />}
onClick={handleMailPaymentReceive}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintPaymentReceive}
/>
<NavbarDivider />
</Can>
<Can I={PaymentReceiveAction.Delete} a={AbilitySubject.PaymentReceive}>
<Button

View File

@@ -63,10 +63,9 @@ export const usePaymentReceiveEntriesColumns = () => {
},
{
Header: intl.get('payment_amount'),
accessor: 'invoice.payment_amount',
Cell: FormatNumberCell,
accessor: 'payment_amount_formatted',
align: 'right',
width: getColumnWidth(entries, 'invoice.payment_amount', {
width: getColumnWidth(entries, 'payment_amount_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -24,6 +24,7 @@ import { useReceiptDetailDrawerContext } from './ReceiptDetailDrawerProvider';
import { SaleReceiptAction, AbilitySubject } from '@/constants/abilityOption';
import { safeCallback, compose } from '@/utils';
import { DRAWERS } from '@/constants/drawers';
import { DialogsName } from '@/constants/dialogs';
/**
* Receipt details actions bar.
@@ -60,6 +61,9 @@ function ReceiptDetailActionBar({
const handleNotifyViaSMS = () => {
openDialog('notify-receipt-via-sms', { receiptId });
};
const handleReceiptMail = () => {
openDialog(DialogsName.ReceiptMail, { receiptId });
};
return (
<DrawerActionsBar>
@@ -74,6 +78,12 @@ function ReceiptDetailActionBar({
<NavbarDivider />
</Can>
<Can I={SaleReceiptAction.View} a={AbilitySubject.Receipt}>
<Button
className={Classes.MINIMAL}
text={'Send Mail'}
icon={<Icon icon="envelope" />}
onClick={handleReceiptMail}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}

View File

@@ -18,12 +18,9 @@ function ReceiptDetailDrawerProvider({ receiptId, ...props }) {
const { featureCan } = useFeatureCan();
// Fetch sale receipt details.
const { data: receipt, isFetching: isReceiptLoading } = useReceipt(
receiptId,
{
enabled: !!receiptId,
},
);
const { data: receipt, isLoading: isReceiptLoading } = useReceipt(receiptId, {
enabled: !!receiptId,
});
// Provider.
const provider = {

View File

@@ -23,7 +23,7 @@ export default function ReceiptDetailTableFooter() {
<ReceiptTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'receipt.details.subtotal'} />}
value={<FormatNumber value={receipt.amount} />}
value={receipt.formatted_amount}
/>
<TotalLine
title={<T id={'receipt.details.total'} />}

View File

@@ -43,8 +43,7 @@ export const useReceiptReadonlyEntriesTableColumns = () => {
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
width: getColumnWidth(entries, 'rate_formatted', {
minWidth: 60,
magicSpacing: 5,
}),
@@ -55,8 +54,7 @@ export const useReceiptReadonlyEntriesTableColumns = () => {
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
width: getColumnWidth(entries, 'total_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -23,7 +23,7 @@ export default function VendorCreditDetailDrawerFooter() {
<VendorCreditTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'vendor_credit.drawer.label_subtotal'} />}
value={<FormatNumber value={vendorCredit.formatted_amount} />}
value={vendorCredit.formatted_amount}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
<TotalLine

View File

@@ -61,9 +61,8 @@ export const useVendorCreditReadonlyEntriesTableColumns = () => {
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
accessor: 'rate_formatted',
width: getColumnWidth(entries, 'rate_formatted', {
minWidth: 60,
magicSpacing: 5,
}),
@@ -73,9 +72,8 @@ export const useVendorCreditReadonlyEntriesTableColumns = () => {
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
accessor: 'total_formatted',
width: getColumnWidth(entries, 'total_formatted', {
minWidth: 60,
magicSpacing: 5,
}),

View File

@@ -284,26 +284,31 @@ export const useComposeRowsOnRemoveTableRow = () => {
/**
* Retrieves the aggregate tax rates from the given item entries.
* @param {string} currencyCode -
* @param {any} taxRates -
* @param {any} entries -
*/
export const aggregateItemEntriesTaxRates = R.curry((taxRates, entries) => {
const taxRatesById = keyBy(taxRates, 'id');
export const aggregateItemEntriesTaxRates = R.curry(
(currencyCode, taxRates, entries) => {
const taxRatesById = keyBy(taxRates, 'id');
// Calculate the total tax amount of invoice entries.
const filteredEntries = entries.filter((e) => e.tax_rate_id);
const groupedTaxRates = groupBy(filteredEntries, 'tax_rate_id');
// Calculate the total tax amount of invoice entries.
const filteredEntries = entries.filter((e) => e.tax_rate_id);
const groupedTaxRates = groupBy(filteredEntries, 'tax_rate_id');
return Object.keys(groupedTaxRates).map((taxRateId) => {
const taxRate = taxRatesById[taxRateId];
const taxRates = groupedTaxRates[taxRateId];
const totalTaxAmount = sumBy(taxRates, 'tax_amount');
const taxAmountFormatted = formattedAmount(totalTaxAmount, 'USD');
return Object.keys(groupedTaxRates).map((taxRateId) => {
const taxRate = taxRatesById[taxRateId];
const taxRates = groupedTaxRates[taxRateId];
const totalTaxAmount = sumBy(taxRates, 'tax_amount');
const taxAmountFormatted = formattedAmount(totalTaxAmount, currencyCode);
return {
taxRateId,
taxRate: taxRate.rate,
label: `${taxRate.name} [${taxRate.rate}%]`,
taxAmount: totalTaxAmount,
taxAmountFormatted,
};
});
});
return {
taxRateId,
taxRate: taxRate.rate,
label: `${taxRate.name} [${taxRate.rate}%]`,
taxAmount: totalTaxAmount,
taxAmountFormatted,
};
});
},
);

View File

@@ -15,6 +15,7 @@ import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import NumberFormatDropdown from '@/components/NumberFormatDropdown';
import { APAgingSummaryExportMenu } from './components';
import withAPAgingSummary from './withAPAgingSummary';
import withAPAgingSummaryActions from './withAPAgingSummaryActions';
@@ -106,11 +107,18 @@ function APAgingSummaryActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<APAgingSummaryExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -27,6 +27,7 @@ function APAgingSummaryProvider({ filter, ...props }) {
isAPAgingLoading,
isAPAgingFetching,
refetch,
query,
};
return (

View File

@@ -1,10 +1,23 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import { useRef } from 'react';
import classNames from 'classnames';
import { If } from '@/components';
import { AppToaster, If, Stack } from '@/components';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { agingSummaryDynamicColumns } from '../AgingSummary/dynamicColumns';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import {
useAPAgingSheetCsvExport,
useAPAgingSheetXlsxExport,
} from '@/hooks/query';
/**
* Retrieve AP aging summary columns.
@@ -29,3 +42,87 @@ export function APAgingSummarySheetLoadingBar() {
</If>
);
}
/**
* A/P aging summary export menu.
* @returns {JSX.Element}
*/
export function APAgingSummaryExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useAPAgingSummaryContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useAPAgingSheetXlsxExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useAPAgingSheetCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -19,6 +19,7 @@ import withARAgingSummaryActions from './withARAgingSummaryActions';
import withARAgingSummary from './withARAgingSummary';
import { compose, safeInvoke } from '@/utils';
import { ARAgingSummaryExportMenu } from './components';
/**
* A/R Aging summary sheet - Actions bar.
@@ -107,11 +108,18 @@ function ARAgingSummaryActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<ARAgingSummaryExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -1,10 +1,23 @@
// @ts-nocheck
import React from 'react';
import React, { useRef } from 'react';
import classNames from 'classnames';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
import { If, FormattedMessage as T } from '@/components';
import { AppToaster, If, Stack, FormattedMessage as T } from '@/components';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { agingSummaryDynamicColumns } from '../AgingSummary/dynamicColumns';
import {
useARAgingSheetCsvExport,
useARAgingSheetXlsxExport,
} from '@/hooks/query';
/**
* Retrieve AR aging summary columns.
@@ -29,3 +42,88 @@ export function ARAgingSummarySheetLoadingBar() {
</If>
);
}
/**
* A/R aging summary export menu.
* @returns {JSX.Element}
*/
export function ARAgingSummaryExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useARAgingSummaryContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useARAgingSheetXlsxExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useARAgingSheetCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -1,5 +1,4 @@
// @ts-nocheck
import React from 'react';
import {
NavbarGroup,
Button,
@@ -13,11 +12,12 @@ import classNames from 'classnames';
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
import NumberFormatDropdown from '@/components/NumberFormatDropdown';
import { BalanceSheetExportMenu } from './components';
import { compose, saveInvoke } from '@/utils';
import { useBalanceSheetContext } from './BalanceSheetProvider';
import withBalanceSheet from './withBalanceSheet';
import withBalanceSheetActions from './withBalanceSheetActions';
import { compose, saveInvoke } from '@/utils';
/**
* Balance sheet - actions bar.
@@ -114,11 +114,18 @@ function BalanceSheetActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<BalanceSheetExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -62,6 +62,7 @@ const BalanceSheetDataTable = styled(ReportDataTable)`
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
color: #252A31;
}
&.is-expanded {
.td:not(.name) .cell-inner {
@@ -72,6 +73,7 @@ const BalanceSheetDataTable = styled(ReportDataTable)`
.td {
font-weight: 500;
border-top: 1px solid #bbb;
color: #000;
}
}

View File

@@ -1,13 +1,32 @@
// @ts-nocheck
import React from 'react';
import { Button } from '@blueprintjs/core';
import React, { useRef } from 'react';
import {
Button,
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { FormattedMessage as T, Icon, If } from '@/components';
import {
FormattedMessage as T,
Icon,
If,
Stack,
AppToaster,
} from '@/components';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { useBalanceSheetContext } from './BalanceSheetProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { dynamicColumns } from './dynamicColumns';
import {
useBalanceSheetCsvExport,
useBalanceSheetXlsxExport,
} from '@/hooks/query';
/**
* Balance sheet alerts.
@@ -66,3 +85,88 @@ export const useBalanceSheetColumns = () => {
[table],
);
};
/**
* Retrieves the balance sheet export menu.
* @returns {JSX.Element}
*/
export const BalanceSheetExportMenu = () => {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useBalanceSheetContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useBalanceSheetXlsxExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useBalanceSheetCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport().then(() => {});
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport().then(() => {});
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
};

View File

@@ -19,6 +19,7 @@ import withCashFlowStatement from './withCashFlowStatement';
import withCashFlowStatementActions from './withCashFlowStatementActions';
import { compose, saveInvoke } from '@/utils';
import { CashflowSheetExportMenu } from './components';
/**
* Cash flow statement actions bar.
@@ -115,11 +116,18 @@ function CashFlowStatementActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<CashflowSheetExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -60,9 +60,8 @@ const CashflowStatementDataTable = styled(DataTable)`
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
color: #252a31;
}
// &.row-type--AGGREGATE,
&.row_type--ACCOUNTS {
border-top: 1px solid #bbb;
}
@@ -72,6 +71,9 @@ const CashflowStatementDataTable = styled(DataTable)`
&.row_type--TOTAL {
font-weight: 500;
.td {
color: #000;
}
&:not(:first-child) .td {
border-top: 1px solid #bbb;
}

View File

@@ -1,8 +1,27 @@
// @ts-nocheck
import React from 'react';
import { Button } from '@blueprintjs/core';
import React, { useRef } from 'react';
import {
Button,
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { Icon, If, FormattedMessage as T } from '@/components';
import {
AppToaster,
Icon,
If,
Stack,
FormattedMessage as T,
} from '@/components';
import {
useCashFlowStatementCsvExport,
useCashFlowStatementXlsxExport,
} from '@/hooks/query';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { dynamicColumns } from './dynamicColumns';
@@ -65,3 +84,88 @@ export function CashFlowStatementAlerts() {
</FinancialComputeAlert>
);
}
/**
* Cashflow sheet export menu.
* @returns {JSX.Element}
*/
export function CashflowSheetExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useCashFlowStatementContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useCashFlowStatementXlsxExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useCashFlowStatementCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -17,6 +17,7 @@ import withCustomersBalanceSummary from './withCustomersBalanceSummary';
import withCustomersBalanceSummaryActions from './withCustomersBalanceSummaryActions';
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
import { compose, saveInvoke } from '@/utils';
import { CustomerBalanceSummaryExportMenu } from './components';
/**
* customer balance summary action bar.
@@ -35,7 +36,7 @@ function CustomersBalanceSummaryActionsBar({
const { refetch, isCustomersBalanceLoading } =
useCustomersBalanceSummaryContext();
// handle filter toggle click.
// Handle filter toggle click.
const handleFilterToggleClick = () => {
toggleCustomerBalanceFilterDrawer();
};
@@ -112,11 +113,18 @@ function CustomersBalanceSummaryActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<CustomerBalanceSummaryExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -31,6 +31,7 @@ function CustomersBalanceSummaryProvider({ filter, ...props }) {
isCustomersBalanceLoading,
refetch,
query
};
return (
<FinancialReportPage name={'customers-balance-summary'}>

View File

@@ -32,7 +32,7 @@ export default function CustomersBalanceSummaryTable({
>
<CustomerBalanceDataTable
columns={columns}
data={table.data}
data={table.rows}
rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true}
sticky={true}

View File

@@ -1,13 +1,25 @@
// @ts-nocheck
import React from 'react';
import React, { useRef } from 'react';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import classNames from 'classnames';
import { If } from '@/components';
import { AppToaster, If, Stack } from '@/components';
import { Align } from '@/constants';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import {
useCustomerBalanceSummaryCsvExport,
useCustomerBalanceSummaryXlsxExport,
} from '@/hooks/query';
/**
* Retrieve customers balance summary columns.
@@ -79,3 +91,89 @@ export function CustomersBalanceLoadingBar() {
</If>
);
}
/**
* Customer balance summary export menu.
*/
export function CustomerBalanceSummaryExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useCustomersBalanceSummaryContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useCustomerBalanceSummaryXlsxExport(
query,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Export the report to csv.
const { mutateAsync: csvExport } = useCustomerBalanceSummaryCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -9,9 +9,10 @@ import {
PopoverInteractionKind,
Position,
} from '@blueprintjs/core';
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
import classNames from 'classnames';
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
import { CustomersTransactionsExportMenu } from './components';
import NumberFormatDropdown from '@/components/NumberFormatDropdown';
import { useCustomersTransactionsContext } from './CustomersTransactionsProvider';
@@ -114,11 +115,18 @@ function CustomersTransactionsActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<CustomersTransactionsExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -1,12 +1,24 @@
// @ts-nocheck
import React from 'react';
import React, { useRef } from 'react';
import intl from 'react-intl-universal';
import { If } from '@/components';
import { AppToaster, If, Stack } from '@/components';
import { Align } from '@/constants';
import { getColumnWidth } from '@/utils';
import { useCustomersTransactionsContext } from './CustomersTransactionsProvider';
import FinancialLoadingBar from '../FinancialLoadingBar';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import {
useCustomersTransactionsCsvExport,
useCustomersTransactionsXlsxExport,
} from '@/hooks/query';
import classNames from 'classnames';
/**
* Retrieve customers transactions columns.
@@ -92,3 +104,91 @@ export function CustomersTransactionsLoadingBar() {
</If>
);
}
/**
* Customers transactions export menu.
* @returns {JSX.Element}
*/
export function CustomersTransactionsExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useCustomersTransactionsContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useCustomersTransactionsXlsxExport(
query,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Export the report to csv.
const { mutateAsync: csvExport } = useCustomersTransactionsCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -12,6 +12,7 @@ import {
import classNames from 'classnames';
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
import { GeneralLedgerSheetExportMenu } from './components';
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import { compose } from '@/utils';
@@ -84,11 +85,18 @@ function GeneralLedgerActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<GeneralLedgerSheetExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -10,7 +10,7 @@ const GeneralLedgerHeaderDimensionsPanelContext = React.createContext();
/**
* General Ledger Header Dimensions Panel provider.
* @returns
* @returns {JSX.Element}
*/
function GeneralLedgerHeaderDimensionsPanelProvider({ query, ...props }) {
// Features guard.

View File

@@ -29,6 +29,7 @@ function GeneralLedgerProvider({ query, ...props }) {
sheetRefresh: refetch,
isFetching,
isLoading,
httpRequest: requestQuery
};
return (
<FinancialReportPage name={'general-ledger-sheet'}>

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import { useMemo } from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
@@ -13,7 +13,7 @@ import {
} from '@/components';
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import { useGeneralLedgerTableColumns } from './components';
import { useGeneralLedgerTableColumns } from './dynamicColumns';
/**
* General ledger table.
@@ -21,7 +21,7 @@ import { useGeneralLedgerTableColumns } from './components';
export default function GeneralLedgerTable({ companyName }) {
// General ledger context.
const {
generalLedger: { tableRows, query },
generalLedger: { query, table },
isLoading,
} = useGeneralLedgerContext();
@@ -30,8 +30,8 @@ export default function GeneralLedgerTable({ companyName }) {
// Default expanded rows of general ledger table.
const expandedRows = useMemo(
() => defaultExpanderReducer(tableRows, 1),
[tableRows],
() => defaultExpanderReducer(table.rows, 1),
[table.rows],
);
return (
@@ -48,7 +48,7 @@ export default function GeneralLedgerTable({ companyName }) {
'this_report_does_not_contain_any_data_between_date_period',
)}
columns={columns}
data={tableRows}
data={table.rows}
rowClassNames={tableRowTypesToClassnames}
expanded={expandedRows}
virtualizedRows={true}
@@ -79,23 +79,20 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
opacity: 0;
}
}
.tr:not(.no-results) .td:not(:first-of-type) {
border-left: 1px solid #ececec;
}
.tr:last-child .td {
border-bottom: 1px solid #ececec;
}
.tr.row_type {
&--ACCOUNT_ROW {
&--ACCOUNT {
.td {
&.date {
font-weight: 500;
.cell-inner {
white-space: nowrap;
position: relative;
position: absolute;
}
}
}
@@ -103,7 +100,6 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
border-top: 1px solid #ddd;
}
}
&--OPENING_BALANCE,
&--CLOSING_BALANCE {
.amount {

View File

@@ -1,107 +1,31 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Button } from '@blueprintjs/core';
import { FormattedMessage as T, Icon, If } from '@/components';
import React, { useRef } from 'react';
import classNames from 'classnames';
import {
Button,
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import {
FormattedMessage as T,
Icon,
If,
Stack,
AppToaster,
} from '@/components';
import { getColumnWidth } from '@/utils';
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { Align } from '@/constants';
/**
* Retrieve the general ledger table columns.
*/
export function useGeneralLedgerTableColumns() {
// General ledger context.
const {
generalLedger: { tableRows },
} = useGeneralLedgerContext();
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: 'date',
className: 'date',
width: 120,
},
{
Header: intl.get('account_name'),
accessor: 'name',
className: 'name',
textOverview: true,
},
{
Header: intl.get('transaction_type'),
accessor: 'reference_type_formatted',
className: 'transaction_type',
width: 125,
textOverview: true,
},
{
Header: intl.get('transaction_number'),
accessor: 'reference_id',
className: 'transaction_number',
width: 100,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'note',
className: 'description',
textOverview: true,
},
{
Header: intl.get('credit'),
accessor: 'formatted_credit',
className: 'credit',
width: getColumnWidth(tableRows, 'formatted_credit', {
minWidth: 100,
magicSpacing: 10,
}),
textOverview: true,
align: Align.Right,
},
{
Header: intl.get('debit'),
accessor: 'formatted_debit',
className: 'debit',
width: getColumnWidth(tableRows, 'formatted_debit', {
minWidth: 100,
magicSpacing: 10,
}),
textOverview: true,
align: Align.Right,
},
{
Header: intl.get('amount'),
accessor: 'formatted_amount',
className: 'amount',
width: getColumnWidth(tableRows, 'formatted_amount', {
minWidth: 100,
magicSpacing: 10,
}),
textOverview: true,
align: Align.Right,
},
{
Header: intl.get('running_balance'),
accessor: 'formatted_running_balance',
className: 'running_balance',
width: getColumnWidth(tableRows, 'formatted_running_balance', {
minWidth: 100,
magicSpacing: 10,
}),
textOverview: true,
align: Align.Right,
},
],
[tableRows],
);
}
import {
useGeneralLedgerSheetCsvExport,
useGeneralLedgerSheetXlsxExport,
} from '@/hooks/query';
/**
* General ledger sheet alerts.
@@ -144,3 +68,93 @@ export function GeneralLedgerSheetLoadingBar() {
</If>
);
}
/**
* Renders the G/L sheet export menu.
* @returns {JSX.Element}
*/
export const GeneralLedgerSheetExportMenu = () => {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { httpRequest } = useGeneralLedgerContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useGeneralLedgerSheetXlsxExport(
httpRequest,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Export the report to csv.
const { mutateAsync: csvExport } = useGeneralLedgerSheetCsvExport(
httpRequest,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
};

View File

@@ -0,0 +1,117 @@
// @ts-nocheck
import { getColumnWidth } from '@/utils';
import * as R from 'ramda';
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import { Align } from '@/constants';
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
const getReportColWidth = (data, accessor, headerText) => {
return getColumnWidth(
data,
accessor,
{ magicSpacing: 10, minWidth: 100 },
headerText,
);
};
/**
* Account name column mapper.
*/
const commonColumnMapper = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
key: column.key,
Header: column.label,
accessor,
className: column.key,
textOverview: true,
};
});
/**
* Numeric columns accessor.
*/
const numericColumnAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
...column,
align: Align.Right,
width,
};
});
/**
* Date column accessor.
*/
const dateColumnAccessor = R.curry((column) => {
return {
...column,
width: 120,
};
});
/**
* Transaction type column accessor.
*/
const transactionTypeColumnAccessor = (column) => {
return {
...column,
width: 125,
};
};
/**
* Transaction number column accessor.
*/
const transactionIdColumnAccessor = (column) => {
return {
...column,
width: 80,
};
};
const dynamiColumnMapper = R.curry((data, column) => {
const _numericColumnAccessor = numericColumnAccessor(data);
return R.compose(
R.when(R.pathEq(['key'], 'date'), dateColumnAccessor),
R.when(
R.pathEq(['key'], 'reference_type'),
transactionTypeColumnAccessor,
),
R.when(
R.pathEq(['key'], 'reference_number'),
transactionIdColumnAccessor,
),
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'amount'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'running_balance'), _numericColumnAccessor),
commonColumnMapper(data),
)(column);
});
/**
* Composes the dynamic columns that fetched from request to columns to table component.
*/
export const dynamicColumns = R.curry((data, columns) => {
return R.map(dynamiColumnMapper(data), columns);
});
/**
* Retrieves the G/L sheet table columns for table component.
*/
export const useGeneralLedgerTableColumns = () => {
const { generalLedger } = useGeneralLedgerContext();
if (!generalLedger) {
throw new Error('asdfadsf');
}
const { table } = generalLedger;
return dynamicColumns(table.rows, table.columns);
};

View File

@@ -13,6 +13,7 @@ import { DashboardActionsBar, Icon, FormattedMessage as T } from '@/components';
import classNames from 'classnames';
import NumberFormatDropdown from '@/components/NumberFormatDropdown';
import { InventoryItemDetailsExportMenu } from './components';
import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider';
import withInventoryItemDetails from './withInventoryItemDetails';
@@ -112,11 +113,18 @@ function InventoryItemDetailsActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<InventoryItemDetailsExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -1,12 +1,32 @@
// @ts-nocheck
import React from 'react';
import { Button } from '@blueprintjs/core';
import { Icon, If, FormattedMessage as T } from '@/components';
import React, { useRef } from 'react';
import {
Button,
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
AppToaster,
Icon,
If,
Stack,
FormattedMessage as T,
} from '@/components';
import { dynamicColumns } from './utils';
import FinancialLoadingBar from '../FinancialLoadingBar';
import {
useInventoryItemDetailsCsvExport,
useInventoryItemDetailsXlsxExport,
} from '@/hooks/query';
import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { useInventoryValuationHttpQuery } from './utils2';
/**
* Retrieve inventory item details columns.
@@ -70,3 +90,94 @@ export function InventoryItemDetailsAlerts() {
</FinancialComputeAlert>
);
}
/**
* Inventory item details export menu.
* @returns {JSX.Element}
*/
export function InventoryItemDetailsExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const httpQuery = useInventoryValuationHttpQuery();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useInventoryItemDetailsXlsxExport(
httpQuery,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Export the report to csv.
const { mutateAsync: csvExport } = useInventoryItemDetailsCsvExport(
httpQuery,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -7,6 +7,7 @@ import intl from 'react-intl-universal';
import { useAppQueryString } from '@/hooks';
import { transformToForm } from '@/utils';
import { transformFilterFormToQuery } from '../common';
/**
* Retrieves inventory item details default query.
@@ -73,3 +74,13 @@ export const useInventoryValuationQuery = () => {
setLocationQuery,
};
};
/**
* Retrieves the inventory valuation http query.
* @returns {Object}
*/
export const useInventoryValuationHttpQuery = () => {
const { query } = useInventoryValuationQuery();
return React.useMemo(() => transformFilterFormToQuery(query), [query]);
};

View File

@@ -18,6 +18,7 @@ import withJournal from './withJournal';
import { compose } from '@/utils';
import { useJournalSheetContext } from './JournalProvider';
import { JournalSheetExportMenu } from './components';
/**
* Journal sheeet - Actions bar.
@@ -85,11 +86,18 @@ function JournalActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<JournalSheetExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -28,6 +28,7 @@ function JournalSheetProvider({ query, ...props }) {
isLoading,
isFetching,
refetchSheet: refetch,
httpQuery: requestQuery
};
return (

View File

@@ -11,10 +11,10 @@ import {
TableVirtualizedListRows,
} from '@/components';
import { useJournalTableColumns } from './components';
import { useJournalSheetContext } from './JournalProvider';
import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils';
import { useJournalSheetColumns } from './dynamicColumns';
/**
* Journal sheet table.
@@ -23,12 +23,12 @@ import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils';
export function JournalTable({ companyName }) {
// Journal sheet context.
const {
journalSheet: { tableRows, query },
journalSheet: { table, query },
isLoading,
} = useJournalSheetContext();
// Retreive the journal table columns.
const columns = useJournalTableColumns();
// Retrieves the journal table columns.
const columns = useJournalSheetColumns();
// Default expanded rows of general journal table.
const expandedRows = useMemo(() => defaultExpanderReducer([], 1), []);
@@ -39,13 +39,13 @@ export function JournalTable({ companyName }) {
sheetType={intl.get('journal_sheet')}
fromDate={query.from_date}
toDate={query.to_date}
name="journal"
loading={isLoading}
fullWidth={true}
name="journal"
>
<JournalDataTable
columns={columns}
data={tableRows}
data={table.rows}
rowClassNames={tableRowTypesToClassnames}
noResults={intl.get(
'this_report_does_not_contain_any_data_between_date_period',
@@ -83,10 +83,9 @@ const JournalDataTable = styled(ReportDataTable)`
border-bottom: 1px solid #dbdbdb;
}
}
.tr.row_type--TOTAL_ENTRIES {
.tr.row_type--TOTAL{
font-weight: 600;
}
.tr:not(.no-results) {
height: 28px;
}

View File

@@ -1,77 +1,31 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import moment from 'moment';
import { Button } from '@blueprintjs/core';
import React, { useRef } from 'react';
import classNames from 'classnames';
import {
Button,
Classes,
Menu,
MenuItem,
ProgressBar,
Text,
Intent,
} from '@blueprintjs/core';
import { Icon, If, FormattedMessage as T } from '@/components';
import {
AppToaster,
Icon,
If,
Stack,
FormattedMessage as T,
} from '@/components';
import { useJournalSheetContext } from './JournalProvider';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { Align } from '@/constants';
/**
* Retrieve the journal table columns.
*/
export const useJournalTableColumns = () => {
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: (row) =>
row.date ? moment(row.date).format('YYYY MMM DD') : '',
className: 'date',
width: 100,
textOverview: true,
},
{
Header: intl.get('transaction_type'),
accessor: 'reference_type_formatted',
className: 'reference_type_formatted',
width: 120,
textOverview: true,
},
{
Header: intl.get('num'),
accessor: 'transaction_number',
className: 'reference_id',
width: 70,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'note',
className: 'note',
textOverview: true,
},
{
Header: intl.get('acc_code'),
accessor: 'account_code',
width: 95,
className: 'account_code',
textOverview: true,
},
{
Header: intl.get('account'),
accessor: 'account_name',
className: 'account_name',
textOverview: true,
},
{
Header: intl.get('credit'),
accessor: 'formatted_credit',
align: Align.Right,
},
{
Header: intl.get('debit'),
accessor: 'formatted_debit',
align: Align.Right,
},
],
[],
);
};
import {
useJournalSheetCsvExport,
useJournalSheetXlsxExport,
} from '@/hooks/query';
/**
* Journal sheet loading bar.
@@ -115,3 +69,87 @@ export function JournalSheetAlerts() {
</FinancialComputeAlert>
);
}
/**
* Retrieves the journal sheet export menu.
* @returns {JSX.Element}
*/
export const JournalSheetExportMenu = () => {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { httpQuery } = useJournalSheetContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Exports the report to xlsx.
const { mutateAsync: xlsxExport } = useJournalSheetXlsxExport(httpQuery, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Exports the report to csv.
const { mutateAsync: csvExport } = useJournalSheetCsvExport(httpQuery, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
};

View File

@@ -0,0 +1,134 @@
// @ts-nocheck
import { Align } from '@/constants';
import { getColumnWidth } from '@/utils';
import * as R from 'ramda';
import { useJournalSheetContext } from './JournalProvider';
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
const getReportColWidth = (data, accessor, headerText) => {
return getColumnWidth(
data,
accessor,
{ magicSpacing: 10, minWidth: 100 },
headerText,
);
};
/**
* Common column mapper.
*/
const commonAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
key: column.key,
Header: column.label,
accessor,
className: column.key,
textOverview: true,
align: Align.Left,
};
});
/**
* Numeric columns accessor.
*/
const numericColumnAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
...column,
align: Align.Right,
width,
};
});
/**
* Date column accessor.
*/
const dateColumnAccessor = (column) => {
return {
...column,
width: 100,
};
};
/**
* Transaction type column accessor.
*/
const transactionTypeColumnAccessor = (column) => {
return {
...column,
width: 120,
};
};
/**
* Transaction number column accessor.
*/
const transactionNumberColumnAccessor = (column) => {
return {
...column,
width: 70,
};
};
/**
* Account code column accessor.
*/
const accountCodeColumnAccessor = (column) => {
return {
...column,
width: 70,
};
};
/**
* Dynamic column mapper.
* @param {} data -
* @param {} column -
*/
const dynamicColumnMapper = R.curry((data, column) => {
const _commonAccessor = commonAccessor(data);
const _numericColumnAccessor = numericColumnAccessor(data);
return R.compose(
R.when(R.pathEq(['key'], 'date'), dateColumnAccessor),
R.when(
R.pathEq(['key'], 'transaction_type'),
transactionTypeColumnAccessor,
),
R.when(
R.pathEq(['key'], 'transaction_number'),
transactionNumberColumnAccessor,
),
R.when(R.pathEq(['key'], 'account_code'), accountCodeColumnAccessor),
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
_commonAccessor,
)(column);
});
/**
* Composes the fetched dynamic columns from the server to the columns to pass it
* to the table component.
*/
export const dynamicColumns = (columns, data) => {
return R.map(dynamicColumnMapper(data), columns);
};
/**
* Retrieves the table columns of journal sheet.
*/
export const useJournalSheetColumns = () => {
const { journalSheet } = useJournalSheetContext();
if (!journalSheet) {
throw new Error('The journal sheet is not loaded');
}
const { table } = journalSheet;
return dynamicColumns(table.columns, table.rows);
};

View File

@@ -1,6 +1,5 @@
// @ts-nocheck
import React from 'react';
import { castArray } from 'lodash';
import moment from 'moment';
import { useAppQueryString } from '@/hooks';

View File

@@ -19,6 +19,7 @@ import withProfitLoss from './withProfitLoss';
import { compose, saveInvoke } from '@/utils';
import { useProfitLossSheetContext } from './ProfitLossProvider';
import { ProfitLossSheetExportMenu } from './components';
/**
* Profit/Loss sheet actions bar.
@@ -110,11 +111,18 @@ function ProfitLossActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<ProfitLossSheetExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import React, { createContext, useContext } from 'react';
import React, { createContext, useContext, useMemo } from 'react';
import FinancialReportPage from '../FinancialReportPage';
import { useProfitLossSheet } from '@/hooks/query';
import { transformFilterFormToQuery } from '../common';
@@ -11,15 +11,18 @@ const ProfitLossSheetContext = createContext();
* @returns {React.JSX}
*/
function ProfitLossSheetProvider({ query, ...props }) {
const innerQuery = useMemo(() => {
return transformFilterFormToQuery(query);
}, [query]);
const {
data: profitLossSheet,
isFetching,
isLoading,
refetch,
} = useProfitLossSheet(
{
...transformFilterFormToQuery(query),
},
innerQuery,
{ keepPreviousData: true },
);
@@ -28,6 +31,7 @@ function ProfitLossSheetProvider({ query, ...props }) {
isLoading,
isFetching,
sheetRefetch: refetch,
query: innerQuery
};
return (

View File

@@ -61,6 +61,7 @@ const ProfitLossDataTable = styled(ReportDataTable)`
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
color: #252A31;
}
&.is-expanded {
.td:not(.name) .cell-inner {
@@ -71,6 +72,7 @@ const ProfitLossDataTable = styled(ReportDataTable)`
.td {
font-weight: 500;
border-top: 1px solid #bbb;
color: #000;
}
}
&:last-of-type .td {

View File

@@ -1,11 +1,29 @@
// @ts-nocheck
import React from 'react';
import { Button } from '@blueprintjs/core';
import { Icon, If, FormattedMessage as T } from '@/components';
import React, { useRef } from 'react';
import {
Button,
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import {
AppToaster,
Icon,
If,
Stack,
FormattedMessage as T,
} from '@/components';
import { useProfitLossSheetContext } from './ProfitLossProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
import FinancialLoadingBar from '../FinancialLoadingBar';
import {
useProfitLossSheetCsvExport,
useProfitLossSheetXlsxExport,
} from '@/hooks/query';
import classNames from 'classnames';
/**
* Profit/loss sheet loading bar.
@@ -50,3 +68,87 @@ export function ProfitLossSheetAlerts() {
</FinancialComputeAlert>
);
}
/**
* Profit/loss sheet export menu.
*/
export const ProfitLossSheetExportMenu = () => {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useProfitLossSheetContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useProfitLossSheetXlsxExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useProfitLossSheetCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
};

View File

@@ -18,6 +18,7 @@ import { compose, saveInvoke } from '@/utils';
import { useSalesTaxLiabilitySummaryContext } from './SalesTaxLiabilitySummaryBoot';
import withSalesTaxLiabilitySummary from './withSalesTaxLiabilitySummary';
import withSalesTaxLiabilitySummaryActions from './withSalesTaxLiabilitySummaryActions';
import { SalesTaxLiabilityExportMenu } from './components';
/**
* Sales tax liability summary - actions bar.
@@ -113,11 +114,18 @@ function SalesTaxLiabilitySummaryActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<SalesTaxLiabilityExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -1,8 +1,22 @@
// @ts-nocheck
import React from 'react';
import { useRef } from 'react';
import classNames from 'classnames';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import { useSalesTaxLiabilitySummaryContext } from './SalesTaxLiabilitySummaryBoot';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { AppToaster, Stack } from '@/components';
import {
useSalesTaxLiabilitySummaryCsvExport,
useSalesTaxLiabilitySummaryXlsxExport,
} from '@/hooks/query';
import { useSalesByItemsContext } from '../SalesByItems/SalesByItemProvider';
/**
* Sales tax liability summary loading bar.
@@ -15,3 +29,94 @@ export function SalesTaxLiabilitySummaryLoadingBar() {
}
return <FinancialLoadingBar />;
}
/**
*
* @returns {JSX.Element}
*/
export function SalesTaxLiabilityExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useSalesTaxLiabilitySummaryContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useSalesTaxLiabilitySummaryXlsxExport(
query,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Export the report to csv.
const { mutateAsync: csvExport } = useSalesTaxLiabilitySummaryCsvExport(
query,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -18,6 +18,7 @@ import withTrialBalance from './withTrialBalance';
import withTrialBalanceActions from './withTrialBalanceActions';
import { compose, saveInvoke } from '@/utils';
import { useTrialBalanceSheetContext } from './TrialBalanceProvider';
import { TrialBalanceSheetExportMenu } from './components';
function TrialBalanceActionsBar({
// #withTrialBalance
@@ -109,11 +110,18 @@ function TrialBalanceActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<TrialBalanceSheetExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -15,7 +15,6 @@ import {
} from './components';
import withTrialBalanceActions from './withTrialBalanceActions';
import { compose } from '@/utils';
/**

View File

@@ -8,8 +8,7 @@ import { tableRowTypesToClassnames } from '@/utils';
import { ReportDataTable, FinancialSheet } from '@/components';
import { useTrialBalanceSheetContext } from './TrialBalanceProvider';
import { useTrialBalanceTableColumns } from './components';
import { useTrialBalanceSheetTableColumns } from './hooks';
/**
* Trial Balance sheet data table.
@@ -17,12 +16,12 @@ import { useTrialBalanceTableColumns } from './components';
export default function TrialBalanceSheetTable({ companyName }) {
// Trial balance sheet context.
const {
trialBalanceSheet: { tableRows, query },
trialBalanceSheet: { table, query },
isLoading,
} = useTrialBalanceSheetContext();
// Trial balance sheet table columns.
const columns = useTrialBalanceTableColumns();
const columns = useTrialBalanceSheetTableColumns();
return (
<FinancialSheet
@@ -36,7 +35,7 @@ export default function TrialBalanceSheetTable({ companyName }) {
>
<TrialBalanceDataTable
columns={columns}
data={tableRows}
data={table.rows}
expandable={true}
expandToggleColumn={1}
expandColumnSpace={1}
@@ -59,7 +58,7 @@ const TrialBalanceDataTable = styled(ReportDataTable)`
.balance.td {
border-top-color: #000;
}
.tr.row_type--total .td {
.tr.row_type--TOTAL .td {
border-top: 1px solid #bbb;
font-weight: 500;
border-bottom: 3px double #000;

View File

@@ -1,87 +1,30 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Button } from '@blueprintjs/core';
import { Align } from '@/constants';
import { getColumnWidth } from '@/utils';
import { CellTextSpan } from '@/components/Datatable/Cells';
import { If, Icon, FormattedMessage as T } from '@/components';
import { useRef } from 'react';
import {
Button,
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
If,
Icon,
FormattedMessage as T,
Stack,
AppToaster,
} from '@/components';
import { useTrialBalanceSheetContext } from './TrialBalanceProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
import FinancialLoadingBar from '../FinancialLoadingBar';
/**
* Retrieves the credit column.
*/
const getCreditColumn = (data) => {
const width = getColumnWidth(data, `credit`, { minWidth: 140 });
return {
Header: intl.get('credit'),
Cell: CellTextSpan,
accessor: 'formatted_credit',
className: 'credit',
width,
textOverview: true,
align: Align.Right,
};
};
/**
* Retrieves the debit column.
*/
const getDebitColumn = (data) => {
return {
Header: intl.get('debit'),
Cell: CellTextSpan,
accessor: 'formatted_debit',
width: getColumnWidth(data, `debit`, { minWidth: 140 }),
textOverview: true,
align: Align.Right,
};
};
/**
* Retrieves the balance column.
*/
const getBalanceColumn = (data) => {
return {
Header: intl.get('balance'),
Cell: CellTextSpan,
accessor: 'formatted_balance',
className: 'balance',
width: getColumnWidth(data, `balance`, { minWidth: 140 }),
textOverview: true,
align: Align.Right,
};
};
/**
* Retrieve trial balance sheet table columns.
*/
export const useTrialBalanceTableColumns = () => {
// Trial balance sheet context.
const {
trialBalanceSheet: { tableRows },
} = useTrialBalanceSheetContext();
return React.useMemo(
() => [
{
Header: intl.get('account_name'),
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
className: 'name',
width: 350,
textOverview: true,
},
getCreditColumn(tableRows),
getDebitColumn(tableRows),
getBalanceColumn(tableRows),
],
[tableRows],
);
};
import {
useTrialBalanceSheetCsvExport,
useTrialBalanceSheetXlsxExport,
} from '@/hooks/query';
import { useTrialBalanceSheetHttpQuery } from './utils';
/**
* Trial balance sheet progress loading bar.
@@ -100,11 +43,8 @@ export function TrialBalanceSheetLoadingBar() {
* Trial balance sheet alerts.
*/
export function TrialBalanceSheetAlerts() {
const {
trialBalanceSheet: { meta },
isLoading,
refetchSheet,
} = useTrialBalanceSheetContext();
const { trialBalanceSheet, isLoading, refetchSheet } =
useTrialBalanceSheetContext();
// Handle refetch the sheet.
const handleRecalcReport = () => {
@@ -115,7 +55,7 @@ export function TrialBalanceSheetAlerts() {
return null;
}
// Can't continue if the cost compute job is not running.
if (!meta.is_cost_compute_running) {
if (!trialBalanceSheet?.meta.is_cost_compute_running) {
return null;
}
@@ -130,3 +70,89 @@ export function TrialBalanceSheetAlerts() {
</FinancialComputeAlert>
);
}
/**
* Trial balance sheet export menu.
*/
export const TrialBalanceSheetExportMenu = () => {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const httpQuery = useTrialBalanceSheetHttpQuery();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useTrialBalanceSheetXlsxExport(
httpQuery,
{
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
},
);
// Export the report to csv.
const { mutateAsync: csvExport } = useTrialBalanceSheetCsvExport(httpQuery, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
};

View File

@@ -0,0 +1,56 @@
// @ts-nocheck
import * as R from 'ramda';
import { Align } from '@/constants';
import { getColumnWidth } from '@/utils';
const ACCOUNT_NAME_COLUMN_WIDTH = 320;
const AMOUNT_COLUMNS_MIN_WIDTH = 120;
const AMOUNT_COLUMNS_MAGIC_SPACING = 10;
const getTableCellValueAccessor = (index: number) => `cells[${index}].value`;
const accountNameAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
Header: column.label,
id: column.key,
accessor,
className: column.key,
width: ACCOUNT_NAME_COLUMN_WIDTH,
};
});
const amountAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
Header: column.label,
id: column.key,
accessor,
className: column.key,
width: getColumnWidth(data, accessor, {
magicSpacing: AMOUNT_COLUMNS_MAGIC_SPACING,
minWidth: AMOUNT_COLUMNS_MIN_WIDTH,
}),
align: Align.Right,
};
});
const dynamicColumnMapper = R.curry((data, column) => {
const accountNameColumn = accountNameAccessor(data);
const creditColumn = amountAccessor(data);
const debitColumn = amountAccessor(data);
const totalColumn = amountAccessor(data);
return R.compose(
R.when(R.pathEq(['key'], 'account_name'), accountNameColumn),
R.when(R.pathEq(['key'], 'credit'), creditColumn),
R.when(R.pathEq(['key'], 'debit'), debitColumn),
R.when(R.pathEq(['key'], 'total'), totalColumn),
)(column);
});
export const trialBalancesheetDynamicColumns = (columns, data) => {
return R.map(dynamicColumnMapper(data), columns);
};

View File

@@ -0,0 +1,18 @@
// @ts-nocheck
import React from 'react';
import { useTrialBalanceSheetContext } from './TrialBalanceProvider';
import { trialBalancesheetDynamicColumns } from './dynamicColumns';
/**
* Retrieves the trial balance sheet columns.
*/
export const useTrialBalanceSheetTableColumns = () => {
const {
trialBalanceSheet: { table },
} = useTrialBalanceSheetContext();
return React.useMemo(
() => trialBalancesheetDynamicColumns(table.columns, table.rows),
[table],
);
};

View File

@@ -5,6 +5,7 @@ import { castArray } from 'lodash';
import { useAppQueryString } from '@/hooks';
import { transformToForm } from '@/utils';
import { transformFilterFormToQuery } from '../common';
/**
* Retrieves the default trial balance query.
@@ -56,3 +57,13 @@ export const useTrialBalanceSheetQuery = () => {
setLocationQuery,
};
};
/**
* Retrieves the trial balance sheet http query.
* @returns {object}
*/
export const useTrialBalanceSheetHttpQuery = () => {
const { query } = useTrialBalanceSheetQuery();
return React.useMemo(() => transformFilterFormToQuery(query), [query]);
};

View File

@@ -19,6 +19,7 @@ import withVendorsBalanceSummaryActions from './withVendorsBalanceSummaryActions
import { useVendorsBalanceSummaryContext } from './VendorsBalanceSummaryProvider';
import { saveInvoke, compose } from '@/utils';
import { VendorSummarySheetExportMenu } from './components';
/**
* Vendors balance summary action bar.
@@ -106,11 +107,18 @@ function VendorsBalanceSummaryActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<VendorSummarySheetExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -31,7 +31,7 @@ export default function VendorsBalanceSummaryTable({
>
<VendorBalanceDataTable
columns={columns}
data={table.data}
data={table.rows}
rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true}
sticky={true}

View File

@@ -1,13 +1,18 @@
// @ts-nocheck
import React from 'react';
import React, { useRef } from 'react';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import classNames from 'classnames';
import { If } from '@/components';
import { Align } from '@/constants';
import { AppToaster, If, Stack } from '@/components';
import { Align, CLASSES } from '@/constants';
import { useVendorsBalanceSummaryContext } from './VendorsBalanceSummaryProvider';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { Intent, Menu, MenuItem, ProgressBar, Text } from '@blueprintjs/core';
import {
useVendorBalanceSummaryCsvExport,
useVendorBalanceSummaryXlsxExport,
} from '@/hooks/query';
/**
* Retrieve vendors balance summary columns.
@@ -85,3 +90,86 @@ export function VendorsSummarySheetLoadingBar() {
</If>
);
}
/**
* Vendor summary sheet export menu.
* @returns {JSX.Element}
*/
export function VendorSummarySheetExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[CLASSES.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useVendorBalanceSummaryXlsxExport({
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useVendorBalanceSummaryCsvExport({
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport().then(() => {});
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport().then(() => {});
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -19,6 +19,7 @@ import withVendorsTransaction from './withVendorsTransaction';
import withVendorsTransactionsActions from './withVendorsTransactionsActions';
import { compose, saveInvoke } from '@/utils';
import { VendorTransactionsExportMenu } from './components';
/**
* vendors transactions actions bar.
@@ -114,11 +115,18 @@ function VendorsTransactionsActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<VendorTransactionsExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
placement="bottom-start"
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -1,11 +1,24 @@
// @ts-nocheck
import React from 'react';
import React, { useRef } from 'react';
import intl from 'react-intl-universal';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { If } from '@/components';
import { AppToaster, If, Stack } from '@/components';
import { useVendorsTransactionsContext } from './VendorsTransactionsProvider';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { getColumnWidth } from '@/utils';
import {
useVendorsTransactionsCsvExport,
useVendorsTransactionsXlsxExport,
} from '@/hooks/query';
/**
* Retrieve vendors transactions columns.
@@ -77,7 +90,7 @@ export const useVendorsTransactionsColumns = () => {
};
/**
* vendors transactions loading bar.
* Vendors transactions loading bar.
*/
export function VendorsTransactionsLoadingBar() {
const { isVendorsTransactionFetching } = useVendorsTransactionsContext();
@@ -88,3 +101,86 @@ export function VendorsTransactionsLoadingBar() {
</If>
);
}
/**
* Vendor transactions export menu.
*/
export function VendorTransactionsExportMenu() {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useVendorsTransactionsContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useVendorsTransactionsXlsxExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useVendorsTransactionsCsvExport({
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
}

View File

@@ -0,0 +1,14 @@
// @ts-nocheck
import { PreferencesCreditNotesBoot } from './PreferencesCreditNotesFormBoot';
import { PreferencesCreditNotesFormPage } from './PreferencesCreditNotesFormPage';
/**
* Credit notes preferences.
*/
export function PreferencesCreditNotes() {
return (
<PreferencesCreditNotesBoot>
<PreferencesCreditNotesFormPage />
</PreferencesCreditNotesBoot>
);
}

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
import * as Yup from 'yup';
const Schema = Yup.object().shape({
termsConditions: Yup.string().optional(),
customerNotes: Yup.string().optional(),
});
export const PreferencesCreditNotesFormSchema = Schema;

View File

@@ -0,0 +1,74 @@
// @ts-nocheck
import styled from 'styled-components';
import { Form } from 'formik';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components';
/**
* Preferences credit notes form.
*/
export function PreferencesCreditNotesForm({ isSubmitting }) {
const history = useHistory();
// Handle close click.
const handleCloseClick = () => {
history.go(-1);
};
return (
<Form>
{/* ---------- Customer Notes ---------- */}
<FFormGroup
name={'customerNotes'}
label={<T id={'pref.creditNotes.customerNotes.field'} />}
fastField={true}
>
<FTextArea
medium={'true'}
name={'customerNotes'}
fastField={true}
fill={true}
/>
</FFormGroup>
{/* ---------- Terms & Conditions ---------- */}
<FFormGroup
name={'termsConditions'}
label={<T id={'pref.creditNotes.termsConditions.field'} />}
fastField={true}
>
<FTextArea
medium={'true'}
name={'termsConditions'}
fastField={true}
fill={true}
/>
</FFormGroup>
<CardFooterActions>
<Button loading={isSubmitting} intent={Intent.PRIMARY} type="submit">
<T id={'save'} />
</Button>
<Button onClick={handleCloseClick}>
<T id={'close'} />
</Button>
</CardFooterActions>
</Form>
);
}
const CardFooterActions = styled.div`
padding-top: 16px;
border-top: 1px solid #e0e7ea;
margin-top: 30px;
.bp4-button {
min-width: 70px;
+ .bp4-button {
margin-left: 10px;
}
}
`;

View File

@@ -0,0 +1,55 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import { useSettings } from '@/hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
import { Card } from '@/components';
const PreferencesCreditNotesFormContext = React.createContext();
function PreferencesCreditNotesBoot({ ...props }) {
// Fetches organization settings.
const { isLoading: isSettingsLoading } = useSettings();
// Provider state.
const provider = {
isSettingsLoading,
};
// Detarmines whether if any query is loading.
const isLoading = isSettingsLoading;
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<PreferencesCreditNotesCard>
{isLoading ? (
<PreferencesPageLoader />
) : (
<PreferencesCreditNotesFormContext.Provider
value={provider}
{...props}
/>
)}
</PreferencesCreditNotesCard>
</div>
);
}
const PreferencesCreditNotesCard = styled(Card)`
padding: 25px;
.bp4-form-group {
max-width: 600px;
}
`;
const usePreferencesCreditNotesFormContext = () =>
React.useContext(PreferencesCreditNotesFormContext);
export { PreferencesCreditNotesBoot, usePreferencesCreditNotesFormContext };

View File

@@ -0,0 +1,82 @@
// @ts-nocheck
import { useEffect } from 'react';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import * as R from 'ramda';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from '@/components';
import { PreferencesCreditNotesFormSchema } from './PreferencesCreditNotesForm.schema';
import { PreferencesCreditNotesForm } from './PreferencesCreditNotesForm';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import { compose, transformToForm, transfromToSnakeCase } from '@/utils';
import withSettings from '@/containers/Settings/withSettings';
import { transferObjectOptionsToArray } from '../Accountant/utils';
import { useSaveSettings } from '@/hooks/query';
const defaultValues = {
termsConditions: '',
customerNotes: '',
};
/**
* Preferences - Credit Notes.
*/
function PreferencesCreditNotesFormPageRoot({
// #withDashboardActions
changePreferencesPageTitle,
// #withSettings
creditNoteSettings,
}) {
// Save settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings();
useEffect(() => {
changePreferencesPageTitle(intl.get('preferences.creditNotes'));
}, [changePreferencesPageTitle]);
// Initial values.
const initialValues = {
...defaultValues,
...transformToForm(creditNoteSettings, defaultValues),
};
// Handle the form submit.
const handleFormSubmit = (values, { setSubmitting }) => {
const options = R.compose(
transferObjectOptionsToArray,
transfromToSnakeCase,
)({ creditNote: { ...values } });
// Handle request success.
const onSuccess = () => {
AppToaster.show({
message: intl.get('preferences.credit_notes.success_message'),
intent: Intent.SUCCESS,
});
setSubmitting(false);
};
// Handle request error.
const onError = () => {
setSubmitting(false);
};
saveSettingMutate({ options }).then(onSuccess).catch(onError);
};
return (
<Formik
initialValues={initialValues}
validationSchema={PreferencesCreditNotesFormSchema}
onSubmit={handleFormSubmit}
component={PreferencesCreditNotesForm}
/>
);
}
export const PreferencesCreditNotesFormPage = compose(
withDashboardActions,
withSettings(({ creditNoteSettings }) => ({
creditNoteSettings: creditNoteSettings,
})),
)(PreferencesCreditNotesFormPageRoot);

View File

@@ -0,0 +1,14 @@
// @ts-nocheck
import { PreferencesEstimatesBoot } from './PreferencesEstimatesFormBoot';
import { PreferencesEstimatesFormPage } from './PreferencesEstimatesFormPage';
/**
* Estimates preferences.
*/
export function PreferencesEstimates() {
return (
<PreferencesEstimatesBoot>
<PreferencesEstimatesFormPage />
</PreferencesEstimatesBoot>
);
}

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
import * as Yup from 'yup';
const Schema = Yup.object().shape({
termsConditions: Yup.string().optional(),
customerNotes: Yup.string().optional(),
});
export const PreferencesEstimatesFormSchema = Schema;

View File

@@ -0,0 +1,74 @@
// @ts-nocheck
import styled from 'styled-components';
import { Form } from 'formik';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components';
/**
* Preferences estimates form.
*/
export function PreferencesEstimatesForm({ isSubmitting }) {
const history = useHistory();
// Handle close click.
const handleCloseClick = () => {
history.go(-1);
};
return (
<Form>
{/* ---------- Customer Notes ---------- */}
<FFormGroup
name={'customerNotes'}
label={<T id={'pref.estimates.customerNotes.field'} />}
fastField={true}
>
<FTextArea
medium={'true'}
name={'customerNotes'}
fastField={true}
fill={true}
/>
</FFormGroup>
{/* ---------- Terms & Conditions ---------- */}
<FFormGroup
name={'termsConditions'}
label={<T id={'pref.estimates.termsConditions.field'} />}
fastField={true}
>
<FTextArea
medium={'true'}
name={'termsConditions'}
fastField={true}
fill={true}
/>
</FFormGroup>
<CardFooterActions>
<Button loading={isSubmitting} intent={Intent.PRIMARY} type="submit">
<T id={'save'} />
</Button>
<Button onClick={handleCloseClick}>
<T id={'close'} />
</Button>
</CardFooterActions>
</Form>
);
}
const CardFooterActions = styled.div`
padding-top: 16px;
border-top: 1px solid #e0e7ea;
margin-top: 30px;
.bp4-button {
min-width: 70px;
+ .bp4-button {
margin-left: 10px;
}
}
`;

View File

@@ -0,0 +1,55 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import { useSettings } from '@/hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
import styled from 'styled-components';
import { Card } from '@/components';
const PreferencesEstimatesFormContext = React.createContext();
function PreferencesEstimatesBoot({ ...props }) {
// Fetches organization settings.
const { isLoading: isSettingsLoading } = useSettings();
// Provider state.
const provider = {
isSettingsLoading,
};
// Detarmines whether if any query is loading.
const isLoading = isSettingsLoading;
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<PreferencesEstimatesCard>
{isLoading ? (
<PreferencesPageLoader />
) : (
<PreferencesEstimatesFormContext.Provider
value={provider}
{...props}
/>
)}
</PreferencesEstimatesCard>
</div>
);
}
const usePreferencesEstimatesFormContext = () =>
React.useContext(PreferencesEstimatesFormContext);
const PreferencesEstimatesCard = styled(Card)`
padding: 25px;
.bp4-form-group {
max-width: 600px;
}
`;
export { PreferencesEstimatesBoot, usePreferencesEstimatesFormContext };

View File

@@ -0,0 +1,82 @@
// @ts-nocheck
import React, { useEffect } from 'react';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import * as R from 'ramda';
import { AppToaster } from '@/components';
import { PreferencesEstimatesFormSchema } from './PreferencesEstimatesForm.schema';
import { PreferencesEstimatesForm } from './PreferencesEstimatesForm';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import withSettings from '@/containers/Settings/withSettings';
import { transferObjectOptionsToArray } from '../Accountant/utils';
import { compose, transformToForm, transfromToSnakeCase } from '@/utils';
import { useSaveSettings } from '@/hooks/query';
const defaultValues = {
termsConditions: '',
customerNotes: '',
};
/**
* Preferences estimates form.
*/
function PreferencesEstimatesFormPageRoot({
// #withDashboardActions
changePreferencesPageTitle,
// #withSettings
estimatesSettings,
}) {
// Save Organization Settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings();
useEffect(() => {
changePreferencesPageTitle(intl.get('preferences.estimates'));
}, [changePreferencesPageTitle]);
// Initial values.
const initialValues = {
...defaultValues,
...transformToForm(estimatesSettings, defaultValues),
};
// Handle the form submit.
const handleFormSubmit = (values, { setSubmitting }) => {
const options = R.compose(
transferObjectOptionsToArray,
transfromToSnakeCase,
)({ salesEstimates: { ...values } });
// Handle request success.
const onSuccess = (response) => {
AppToaster.show({
message: intl.get('preferences.estimates.success_message'),
intent: Intent.SUCCESS,
});
setSubmitting(false);
};
// Handle request error.
const onError = () => {
setSubmitting(false);
};
saveSettingMutate({ options }).then(onSuccess).catch(onError);
};
return (
<Formik
initialValues={initialValues}
validationSchema={PreferencesEstimatesFormSchema}
onSubmit={handleFormSubmit}
component={PreferencesEstimatesForm}
/>
);
}
export const PreferencesEstimatesFormPage = compose(
withDashboardActions,
withSettings(({ estimatesSettings }) => ({
estimatesSettings: estimatesSettings,
})),
)(PreferencesEstimatesFormPageRoot);

View File

@@ -6,6 +6,9 @@ const Schema = Yup.object().shape({
name: Yup.string()
.required()
.label(intl.get('organization_name_')),
tax_number: Yup.string()
.nullable()
.label(intl.get('organization_tax_number_')),
industry: Yup.string()
.nullable()
.label(intl.get('organization_industry_')),

View File

@@ -59,6 +59,17 @@ export default function PreferencesGeneralForm({ isSubmitting }) {
<FInputGroup medium={'true'} name={'name'} fastField={true} />
</FFormGroup>
{/* ---------- Organization Tax Number ---------- */}
<FFormGroup
name={'tax_number'}
label={<T id={'organization_tax_number'} />}
inline={true}
helperText={<T id={'shown_on_sales_forms_and_purchase_orders'} />}
fastField={true}
>
<FInputGroup medium={'true'} name={'tax_number'} fastField={true} />
</FFormGroup>
{/* ---------- Industry ---------- */}
<FFormGroup
name={'industry'}

View File

@@ -23,6 +23,7 @@ const defaultValues = {
fiscal_year: '',
date_format: '',
timezone: '',
tax_number: '',
};
/**

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
import * as Yup from 'yup';
const Schema = Yup.object().shape({
termsConditions: Yup.string().optional(),
customerNotes: Yup.string().optional(),
});
export const PreferencesInvoiceFormSchema = Schema;

View File

@@ -0,0 +1,53 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from '@/constants/classes';
import { useSettings } from '@/hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
import { Card } from '@/components';
const PreferencesInvoiceFormContext = React.createContext();
function PreferencesInvoicesBoot({ ...props }) {
// Fetches organization settings.
const { isLoading: isSettingsLoading } = useSettings();
// Provider state.
const provider = {
isSettingsLoading
};
// Detarmines whether if any query is loading.
const isLoading = isSettingsLoading;
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<PreferencesInvoicesCard>
{isLoading ? (
<PreferencesPageLoader />
) : (
<PreferencesInvoiceFormContext.Provider value={provider} {...props} />
)}
</PreferencesInvoicesCard>
</div>
);
}
const PreferencesInvoicesCard = styled(Card)`
padding: 25px;
.bp4-form-group{
max-width: 600px;
}
`;
const usePreferencesInvoiceFormContext = () =>
React.useContext(PreferencesInvoiceFormContext);
export { PreferencesInvoicesBoot, usePreferencesInvoiceFormContext };

View File

@@ -0,0 +1,82 @@
// @ts-nocheck
import React, { useEffect } from 'react';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import * as R from 'ramda';
import { AppToaster } from '@/components';
import { PreferencesInvoiceFormSchema } from './PreferencesInvoiceForm.schema';
import { PreferencesInvoicesForm } from './PreferencesInvoicesForm';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import { compose, transformToForm, transfromToSnakeCase } from '@/utils';
import withSettings from '@/containers/Settings/withSettings';
import { transferObjectOptionsToArray } from '../Accountant/utils';
import { useSaveSettings } from '@/hooks/query';
const defaultValues = {
termsConditions: '',
customerNotes: '',
};
/**
* Preferences - Invoices.
*/
function PreferencesInvoiceFormPage({
// #withDashboardActions
changePreferencesPageTitle,
// #withSettings
invoiceSettings,
}) {
// Save settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings();
useEffect(() => {
changePreferencesPageTitle(intl.get('preferences.invoices'));
}, [changePreferencesPageTitle]);
// Initial values.
const initialValues = {
...defaultValues,
...transformToForm(invoiceSettings, defaultValues),
};
// Handle the form submit.
const handleFormSubmit = (values, { setSubmitting }) => {
const options = R.compose(
transferObjectOptionsToArray,
transfromToSnakeCase,
)({ salesInvoices: { ...values } });
// Handle request success.
const onSuccess = () => {
AppToaster.show({
message: intl.get('preferences.invoices.success_message'),
intent: Intent.SUCCESS,
});
setSubmitting(false);
};
// Handle request error.
const onError = () => {
setSubmitting(false);
};
saveSettingMutate({ options }).then(onSuccess).catch(onError);
};
return (
<Formik
initialValues={initialValues}
validationSchema={PreferencesInvoiceFormSchema}
onSubmit={handleFormSubmit}
component={PreferencesInvoicesForm}
/>
);
}
export default compose(
withDashboardActions,
withSettings(({ invoiceSettings }) => ({
invoiceSettings: invoiceSettings,
})),
)(PreferencesInvoiceFormPage);

View File

@@ -0,0 +1,14 @@
// @ts-nocheck
import { PreferencesInvoicesBoot } from './PreferencesInvoiceFormBoot';
import PreferencesInvoiceFormPage from './PreferencesInvoiceFormPage';
/**
* items preferences.
*/
export default function PreferencesInvoices() {
return (
<PreferencesInvoicesBoot>
<PreferencesInvoiceFormPage />
</PreferencesInvoicesBoot>
);
}

View File

@@ -0,0 +1,74 @@
// @ts-nocheck
import styled from 'styled-components';
import { Form } from 'formik';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components';
/**
* Invoices preferences form.
*/
export function PreferencesInvoicesForm({ isSubmitting }) {
const history = useHistory();
// Handle close click.
const handleCloseClick = () => {
history.go(-1);
};
return (
<Form>
{/* ---------- Customer Notes ---------- */}
<FFormGroup
name={'customerNotes'}
label={<T id={'pref.invoices.customerNotes.field'} />}
fastField={true}
>
<FTextArea
medium={'true'}
name={'customerNotes'}
fastField={true}
fill={true}
/>
</FFormGroup>
{/* ---------- Terms & Conditions ---------- */}
<FFormGroup
name={'termsConditions'}
label={<T id={'pref.invoices.termsConditions.field'} />}
fastField={true}
>
<FTextArea
medium={'true'}
name={'termsConditions'}
fastField={true}
fill={true}
/>
</FFormGroup>
<CardFooterActions>
<Button loading={isSubmitting} intent={Intent.PRIMARY} type="submit">
<T id={'save'} />
</Button>
<Button onClick={handleCloseClick}>
<T id={'close'} />
</Button>
</CardFooterActions>
</Form>
);
}
const CardFooterActions = styled.div`
padding-top: 16px;
border-top: 1px solid #e0e7ea;
margin-top: 30px;
.bp4-button {
min-width: 70px;
+ .bp4-button {
margin-left: 10px;
}
}
`;

View File

@@ -0,0 +1,14 @@
// @ts-nocheck
import { PreferencesReceiptsBoot } from './PreferencesReceiptsFormBoot';
import { PreferencesReceiptsFormPage } from './PreferencesReceiptsFormPage';
/**
* Preferences - Receipts.
*/
export function PreferencesReceipts() {
return (
<PreferencesReceiptsBoot>
<PreferencesReceiptsFormPage />
</PreferencesReceiptsBoot>
);
}

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
import * as Yup from 'yup';
const Schema = Yup.object().shape({
termsConditions: Yup.string().optional(),
customerNotes: Yup.string().optional(),
});
export const PreferencesReceiptsFormSchema = Schema;

Some files were not shown because too many files have changed in this diff Show More