From a7df23cebcdb698b807f36314e218e350f84b8d6 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 11 Sep 2024 21:16:21 +0200 Subject: [PATCH] feat: branding templates table --- packages/server/src/api/index.ts | 2 +- .../PdfTemplate/GetPdfTemplatesTransformer.ts | 19 +++- packages/webapp/src/components/Card/index.tsx | 8 +- .../src/components/DrawersContainer.tsx | 9 +- packages/webapp/src/constants/drawers.ts | 4 +- .../containers/AlertsContainer/registered.tsx | 2 + .../BrandTemplates.module.scss | 15 ++++ .../BrandingTemplatesActionsBar.tsx | 35 ++++++++ .../BrandingTemplatesBoot.tsx | 32 +++++++ .../BrandingTemplatesContent.tsx | 52 +++++++++++ .../BrandingTemplatesDrawer.tsx | 37 ++++++++ .../BrandingTemplatesTable.tsx | 87 +++++++++++++++++++ .../BrandingTemplates/_components.tsx | 26 ++++++ .../alerts/BrandingTemplatesAlerts.ts | 10 +++ .../alerts/DeleteBrandingTemplateAlert.tsx | 72 +++++++++++++++ .../InvoiceCustomize/InvoiceCustomize.tsx | 10 +-- .../InvoicesLanding/InvoicesActionsBar.tsx | 2 +- .../webapp/src/hooks/query/pdf-templates.ts | 1 + .../style/components/DataTable/DataTable.scss | 2 +- .../src/style/pages/Dashboard/Dashboard.scss | 9 ++ 20 files changed, 418 insertions(+), 16 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index c0fc2ef80..9dc3b5d07 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -149,7 +149,7 @@ export default () => { dashboard.use('/export', Container.get(ExportController).router()); dashboard.use('/attachments', Container.get(AttachmentsController).router()); dashboard.use( - '/pdf_templates', + '/pdf-templates', Container.get(PdfTemplatesController).router() ); diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts index 9863281cb..181b3f029 100644 --- a/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts +++ b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts @@ -1,8 +1,7 @@ import { Transformer } from '@/lib/Transformer/Transformer'; +import { getTransactionTypeLabel } from '@/utils/transactions-types'; export class GetPdfTemplatesTransformer extends Transformer { - // Empty transformer with no additional methods or attributes - /** * Exclude attributes. * @returns {string[]} @@ -10,4 +9,20 @@ export class GetPdfTemplatesTransformer extends Transformer { public excludeAttributes = (): string[] => { return ['attributes']; }; + + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return ['createdAtFormatted', 'resourceFormatted']; + }; + + private createdAtFormatted = (template) => { + return this.formatDate(template.createdAt); + }; + + private resourceFormatted = (template) => { + return getTransactionTypeLabel(template.resource); + }; } diff --git a/packages/webapp/src/components/Card/index.tsx b/packages/webapp/src/components/Card/index.tsx index 8bd314db6..da2c64b43 100644 --- a/packages/webapp/src/components/Card/index.tsx +++ b/packages/webapp/src/components/Card/index.tsx @@ -2,8 +2,12 @@ import React from 'react'; import styled from 'styled-components'; -export function Card({ className, children }) { - return {children}; +export function Card({ className, style, children }) { + return ( + + {children} + + ); } const CardRoot = styled.div` diff --git a/packages/webapp/src/components/DrawersContainer.tsx b/packages/webapp/src/components/DrawersContainer.tsx index 3e28d06af..fdcaac652 100644 --- a/packages/webapp/src/components/DrawersContainer.tsx +++ b/packages/webapp/src/components/DrawersContainer.tsx @@ -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() { - + + ); } diff --git a/packages/webapp/src/constants/drawers.ts b/packages/webapp/src/constants/drawers.ts index 928d9c189..843212d8e 100644 --- a/packages/webapp/src/constants/drawers.ts +++ b/packages/webapp/src/constants/drawers.ts @@ -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' } diff --git a/packages/webapp/src/containers/AlertsContainer/registered.tsx b/packages/webapp/src/containers/AlertsContainer/registered.tsx index ba2d44e41..5094b8783 100644 --- a/packages/webapp/src/containers/AlertsContainer/registered.tsx +++ b/packages/webapp/src/containers/AlertsContainer/registered.tsx @@ -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, ]; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss new file mode 100644 index 000000000..b9ae04f02 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx new file mode 100644 index 000000000..65aa94e3e --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx @@ -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 ( + + + + + + ); +} +export const BrandingTemplateActionsBar = compose(withDrawerActions)( + BrandingTemplateActionsBarRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx new file mode 100644 index 000000000..3a1470253 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx @@ -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( + {} as BrandingTemplatesBootValues, +); + +interface BrandingTemplatesBootProps { + children: React.ReactNode; +} + +function BrandingTemplatesBoot({ ...props }: BrandingTemplatesBootProps) { + const { data: pdfTemplates, isLoading: isPdfTemplatesLoading } = + useGetPdfTemplates(); + + const provider = { + pdfTemplates, + isPdfTemplatesLoading, + } as BrandingTemplatesBootValues; + + return ; +} + +const useBrandingTemplatesBoot = () => + React.useContext(BrandingTemplatesBootContext); + +export { BrandingTemplatesBoot, useBrandingTemplatesBoot }; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx new file mode 100644 index 000000000..cffad144f --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx @@ -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 ( + + + + + + + + + + + + + ); +} + +const BrandingTemplateHeader = R.compose(withDrawerActions)( + ({ openDrawer }) => { + const handleCreateBtnClick = () => { + openDrawer(DRAWERS.INVOICE_CUSTOMIZE); + }; + return ( + + + + ); + }, +); + +BrandingTemplateHeader.displayName = 'BrandingTemplateHeader'; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx new file mode 100644 index 000000000..afc51c22c --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx @@ -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 ( + + + + + + ); +} + +export const BrandingTemplatesDrawer = R.compose(withDrawers())( + BrandingTemplatesDrawerRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx new file mode 100644 index 000000000..07164d26b --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx @@ -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 ( + + ); +} + +export const BrandingTemplatesTable = R.compose( + withAlertActions, + withDrawerActions, +)(BrandingTemplateTableRoot); + +const useBrandingTemplatesColumns = () => { + return [ + { + Header: 'Template Name', + accessor: (row) => ( + + {row.template_name} Default + + ), + width: 65, + clickable: true, + }, + { + Header: 'Created At', + accessor: 'created_at_formatted', + width: 35, + className: clsx(Classes.TEXT_MUTED), + clickable: true, + }, + ]; +}; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx new file mode 100644 index 000000000..7366e3556 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx @@ -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 ( + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts new file mode 100644 index 000000000..9bedcf82e --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts @@ -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 }, +]; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx new file mode 100644 index 000000000..a044d5fbe --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx @@ -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 ( + +

+ Are you sure want to delete branding template? +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(DeleteBrandingTemplateAlert); + + diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx index c42dc2e44..966a3cf9e 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx @@ -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); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx index be63b7637..de71f269c 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx @@ -109,7 +109,7 @@ function InvoiceActionsBar({ // Handles the invoice customize button click. const handleCustomizeBtnClick = () => { - openDrawer(DRAWERS.INVOICE_CUSTOMIZE); + openDrawer(DRAWERS.BRANDING_TEMPLATES); }; return ( diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts index 2045c0fb3..d01673500 100644 --- a/packages/webapp/src/hooks/query/pdf-templates.ts +++ b/packages/webapp/src/hooks/query/pdf-templates.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { useMutation, useQuery, diff --git a/packages/webapp/src/style/components/DataTable/DataTable.scss b/packages/webapp/src/style/components/DataTable/DataTable.scss index a96d7a003..3d6387028 100644 --- a/packages/webapp/src/style/components/DataTable/DataTable.scss +++ b/packages/webapp/src/style/components/DataTable/DataTable.scss @@ -20,7 +20,7 @@ .th { padding: 0.68rem 0.5rem; - background: #f5f5f5; + background: #f6f7f9; font-size: 14px; color: #424853; font-weight: 400; diff --git a/packages/webapp/src/style/pages/Dashboard/Dashboard.scss b/packages/webapp/src/style/pages/Dashboard/Dashboard.scss index a6ea3968f..02a7c0049 100644 --- a/packages/webapp/src/style/pages/Dashboard/Dashboard.scss +++ b/packages/webapp/src/style/pages/Dashboard/Dashboard.scss @@ -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;