mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
feat: branding templates table
This commit is contained in:
@@ -2,8 +2,12 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Card({ className, children }) {
|
||||
return <CardRoot className={className}>{children}</CardRoot>;
|
||||
export function Card({ className, style, children }) {
|
||||
return (
|
||||
<CardRoot className={className} style={style}>
|
||||
{children}
|
||||
</CardRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const CardRoot = styled.div`
|
||||
|
||||
@@ -27,9 +27,11 @@ import { InvoiceCustomizeDrawer } from '@/containers/Sales/Invoices/InvoiceCusto
|
||||
import { EstimateCustomizeDrawer } from '@/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer';
|
||||
import { ReceiptCustomizeDrawer } from '@/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer';
|
||||
import { CreditNoteCustomizeDrawer } from '@/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer';
|
||||
import { PaymentReceivedCustomizeDrawer } from '@/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer';
|
||||
import { BrandingTemplatesDrawer } from '@/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer';
|
||||
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import { PaymentReceivedCustomizeDrawer } from '@/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer';
|
||||
|
||||
/**
|
||||
* Drawers container of the dashboard.
|
||||
*/
|
||||
@@ -73,7 +75,10 @@ export default function DrawersContainer() {
|
||||
<EstimateCustomizeDrawer name={DRAWERS.ESTIMATE_CUSTOMIZE} />
|
||||
<ReceiptCustomizeDrawer name={DRAWERS.RECEIPT_CUSTOMIZE} />
|
||||
<CreditNoteCustomizeDrawer name={DRAWERS.CREDIT_NOTE_CUSTOMIZE} />
|
||||
<PaymentReceivedCustomizeDrawer name={DRAWERS.PAYMENT_RECEIVED_CUSTOMIZE} />
|
||||
<PaymentReceivedCustomizeDrawer
|
||||
name={DRAWERS.PAYMENT_RECEIVED_CUSTOMIZE}
|
||||
/>
|
||||
<BrandingTemplatesDrawer name={DRAWERS.BRANDING_TEMPLATES} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,6 @@ export enum DRAWERS {
|
||||
PAYMENT_RECEIPT_CUSTOMIZE = 'PAYMENT_RECEIPT_CUSTOMIZE',
|
||||
RECEIPT_CUSTOMIZE = 'RECEIPT_CUSTOMIZE',
|
||||
CREDIT_NOTE_CUSTOMIZE = 'CREDIT_NOTE_CUSTOMIZE',
|
||||
PAYMENT_RECEIVED_CUSTOMIZE = 'PAYMENT_RECEIVED_CUSTOMIZE'
|
||||
|
||||
PAYMENT_RECEIVED_CUSTOMIZE = 'PAYMENT_RECEIVED_CUSTOMIZE',
|
||||
BRANDING_TEMPLATES = 'BRANDING_TEMPLATES'
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CashflowAlerts } from '../CashFlow/CashflowAlerts';
|
||||
import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts';
|
||||
import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts';
|
||||
import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts';
|
||||
import { BrandingTemplatesAlerts } from '../Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts';
|
||||
|
||||
export default [
|
||||
...AccountsAlerts,
|
||||
@@ -61,4 +62,5 @@ export default [
|
||||
...BankRulesAlerts,
|
||||
...SubscriptionAlerts,
|
||||
...BankAccountAlerts,
|
||||
...BrandingTemplatesAlerts,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
.table {
|
||||
:global {
|
||||
.table .tbody .tr .td{
|
||||
padding-top: 14px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.table .thead .th{
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Button, NavbarGroup, Intent } from '@blueprintjs/core';
|
||||
import { DashboardActionsBar, Icon } from '@/components';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Account drawer action bar.
|
||||
*/
|
||||
function BrandingTemplateActionsBarRoot({ openDrawer }) {
|
||||
// Handle new child button click.
|
||||
const handleCreateBtnClick = () => {
|
||||
openDrawer(DRAWERS.INVOICE_CUSTOMIZE);
|
||||
};
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
icon={<Icon icon="plus" />}
|
||||
onClick={handleCreateBtnClick}
|
||||
minimal
|
||||
>
|
||||
Create Invoice Branding
|
||||
</Button>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
export const BrandingTemplateActionsBar = compose(withDrawerActions)(
|
||||
BrandingTemplateActionsBarRoot,
|
||||
);
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { createContext } from 'react';
|
||||
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
|
||||
|
||||
interface BrandingTemplatesBootValues {
|
||||
pdfTemplates: any;
|
||||
isPdfTemplatesLoading: boolean;
|
||||
}
|
||||
|
||||
const BrandingTemplatesBootContext = createContext<BrandingTemplatesBootValues>(
|
||||
{} as BrandingTemplatesBootValues,
|
||||
);
|
||||
|
||||
interface BrandingTemplatesBootProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function BrandingTemplatesBoot({ ...props }: BrandingTemplatesBootProps) {
|
||||
const { data: pdfTemplates, isLoading: isPdfTemplatesLoading } =
|
||||
useGetPdfTemplates();
|
||||
|
||||
const provider = {
|
||||
pdfTemplates,
|
||||
isPdfTemplatesLoading,
|
||||
} as BrandingTemplatesBootValues;
|
||||
|
||||
return <BrandingTemplatesBootContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
const useBrandingTemplatesBoot = () =>
|
||||
React.useContext<BrandingTemplatesBootValues>(BrandingTemplatesBootContext);
|
||||
|
||||
export { BrandingTemplatesBoot, useBrandingTemplatesBoot };
|
||||
@@ -0,0 +1,52 @@
|
||||
// @ts-nocheck
|
||||
import * as R from 'ramda';
|
||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
||||
import { BrandingTemplatesBoot } from './BrandingTemplatesBoot';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
DrawerBody,
|
||||
DrawerHeaderContent,
|
||||
Group,
|
||||
} from '@/components';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import { BrandingTemplatesTable } from './BrandingTemplatesTable';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import { BrandingTemplateActionsBar } from './BrandingTemplatesActionsBar';
|
||||
|
||||
export default function BrandingTemplateContent() {
|
||||
return (
|
||||
<Box>
|
||||
<DrawerHeaderContent
|
||||
name={DRAWERS.BRANDING_TEMPLATES}
|
||||
title={'Branding Templates'}
|
||||
/>
|
||||
<Box className={Classes.DRAWER_BODY}>
|
||||
<BrandingTemplatesBoot>
|
||||
<BrandingTemplateActionsBar />
|
||||
|
||||
<Card style={{ padding: 0 }}>
|
||||
<BrandingTemplatesTable />
|
||||
</Card>
|
||||
</BrandingTemplatesBoot>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const BrandingTemplateHeader = R.compose(withDrawerActions)(
|
||||
({ openDrawer }) => {
|
||||
const handleCreateBtnClick = () => {
|
||||
openDrawer(DRAWERS.INVOICE_CUSTOMIZE);
|
||||
};
|
||||
return (
|
||||
<Group>
|
||||
<Button intent={Intent.PRIMARY} onClick={handleCreateBtnClick}>
|
||||
Create Invoice Branding
|
||||
</Button>
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BrandingTemplateHeader.displayName = 'BrandingTemplateHeader';
|
||||
@@ -0,0 +1,37 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import { Drawer, DrawerSuspense } from '@/components';
|
||||
import withDrawers from '@/containers/Drawer/withDrawers';
|
||||
|
||||
const BrandingTemplatesContent = React.lazy(
|
||||
() => import('./BrandingTemplatesContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Invoice customize drawer.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function BrandingTemplatesDrawerRoot({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: {},
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
size={'600px'}
|
||||
style={{ borderLeftColor: '#cbcbcb' }}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<BrandingTemplatesContent />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export const BrandingTemplatesDrawer = R.compose(withDrawers())(
|
||||
BrandingTemplatesDrawerRoot,
|
||||
);
|
||||
@@ -0,0 +1,87 @@
|
||||
// @ts-nocheck
|
||||
import * as R from 'ramda';
|
||||
import { Classes, Tag } from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
import { DataTable, Group, TableSkeletonRows } from '@/components';
|
||||
import { useBrandingTemplatesBoot } from './BrandingTemplatesBoot';
|
||||
import { ActionsMenu } from './_components';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import styles from './BrandTemplates.module.scss';
|
||||
|
||||
interface BrandingTemplatesTableProps {}
|
||||
|
||||
function BrandingTemplateTableRoot({
|
||||
openAlert,
|
||||
openDrawer,
|
||||
}: BrandingTemplatesTableProps) {
|
||||
// Table columns.
|
||||
const columns = useBrandingTemplatesColumns();
|
||||
|
||||
const { isPdfTemplatesLoading, pdfTemplates } = useBrandingTemplatesBoot();
|
||||
|
||||
const handleEditTemplate = (template) => {
|
||||
openDrawer(DRAWERS.INVOICE_CUSTOMIZE, {
|
||||
templateId: template.id,
|
||||
resource: template.resource,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteTemplate = (template) => {
|
||||
openAlert('branding-template-delete', { templateId: template.id });
|
||||
};
|
||||
|
||||
const handleCellClick = (cell, event) => {
|
||||
const templateId = cell.row.original.id;
|
||||
const resource = cell.row.original.resource;
|
||||
|
||||
openDrawer(DRAWERS.INVOICE_CUSTOMIZE, { templateId, resource });
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={pdfTemplates || []}
|
||||
loading={isPdfTemplatesLoading}
|
||||
progressBarLoading={isPdfTemplatesLoading}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
ContextMenu={ActionsMenu}
|
||||
noInitialFetch={true}
|
||||
payload={{
|
||||
onDeleteTemplate: handleDeleteTemplate,
|
||||
onEditTemplate: handleEditTemplate,
|
||||
}}
|
||||
rowContextMenu={ActionsMenu}
|
||||
onCellClick={handleCellClick}
|
||||
className={styles.table}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const BrandingTemplatesTable = R.compose(
|
||||
withAlertActions,
|
||||
withDrawerActions,
|
||||
)(BrandingTemplateTableRoot);
|
||||
|
||||
const useBrandingTemplatesColumns = () => {
|
||||
return [
|
||||
{
|
||||
Header: 'Template Name',
|
||||
accessor: (row) => (
|
||||
<Group spacing={10}>
|
||||
{row.template_name} <Tag round>Default</Tag>
|
||||
</Group>
|
||||
),
|
||||
width: 65,
|
||||
clickable: true,
|
||||
},
|
||||
{
|
||||
Header: 'Created At',
|
||||
accessor: 'created_at_formatted',
|
||||
width: 35,
|
||||
className: clsx(Classes.TEXT_MUTED),
|
||||
clickable: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
// @ts-nocheck
|
||||
import { safeCallback } from '@/utils';
|
||||
import { Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Templates table actions menu.
|
||||
*/
|
||||
export function ActionsMenu({
|
||||
row: { original },
|
||||
payload: { onDeleteTemplate, onEditTemplate },
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text={'Edit Template'}
|
||||
onClick={safeCallback(onEditTemplate, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={'Delete Template'}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDeleteTemplate, original)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
const DeleteBrandingTemplateAlert = React.lazy(
|
||||
() => import('./DeleteBrandingTemplateAlert'),
|
||||
);
|
||||
|
||||
export const BrandingTemplatesAlerts = [
|
||||
{ name: 'branding-template-delete', component: DeleteBrandingTemplateAlert },
|
||||
];
|
||||
@@ -0,0 +1,72 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { AppToaster } from '@/components';
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
import { useDeletePdfTemplate} from '@/hooks/query/pdf-templates';
|
||||
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Delete branding template alert.
|
||||
*/
|
||||
function DeleteBrandingTemplateAlert({
|
||||
// #ownProps
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { templateId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: deleteBrandingTemplateMutate } = useDeletePdfTemplate();
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
deleteBrandingTemplateMutate({ templateId })
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The branding template has been deleted successfully.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeAlert(name);
|
||||
})
|
||||
.catch((error) => {
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong.',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={intl.get('cancel')}
|
||||
confirmButtonText={intl.get('delete')}
|
||||
intent={Intent.DANGER}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancel}
|
||||
onConfirm={handleConfirmDelete}
|
||||
>
|
||||
<p>
|
||||
Are you sure want to delete branding template?
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(DeleteBrandingTemplateAlert);
|
||||
|
||||
|
||||
@@ -41,11 +41,11 @@ export default function InvoiceCustomizeContent() {
|
||||
const reqValues = transformToEditRequest(values, templateId);
|
||||
|
||||
// Edit existing template
|
||||
editPdfTemplate({ ...reqValues })
|
||||
.then(() => handleSuccess('PDF template updated successfully!'))
|
||||
.catch(() =>
|
||||
handleError('An error occurred while updating the PDF template.'),
|
||||
);
|
||||
// editPdfTemplate({ templateId, values: reqValues })
|
||||
// .then(() => handleSuccess('PDF template updated successfully!'))
|
||||
// .catch(() =>
|
||||
// handleError('An error occurred while updating the PDF template.'),
|
||||
// );
|
||||
} else {
|
||||
const reqValues = transformToNewRequest(values);
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ function InvoiceActionsBar({
|
||||
|
||||
// Handles the invoice customize button click.
|
||||
const handleCustomizeBtnClick = () => {
|
||||
openDrawer(DRAWERS.INVOICE_CUSTOMIZE);
|
||||
openDrawer(DRAWERS.BRANDING_TEMPLATES);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
.th {
|
||||
padding: 0.68rem 0.5rem;
|
||||
background: #f5f5f5;
|
||||
background: #f6f7f9;
|
||||
font-size: 14px;
|
||||
color: #424853;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -232,6 +232,15 @@ $dashboard-views-bar-height: 44px;
|
||||
&.#{$ns}-minimal.#{$ns}-intent-warning{
|
||||
color: #cc7e25;
|
||||
}
|
||||
&.#{$ns}-minimal.#{$ns}-intent-primary{
|
||||
color: #223fc5;
|
||||
|
||||
&:hover,
|
||||
&:focus{
|
||||
color: #223fc5;
|
||||
background: rgba(34, 63, 197, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
&.button--blue-highlight {
|
||||
background-color: #ebfaff;
|
||||
|
||||
Reference in New Issue
Block a user