feat: invoice pdf preview dialog.

This commit is contained in:
a.bouhuolia
2021-08-17 19:00:35 +02:00
parent f7c3244145
commit fc62aca56e
15 changed files with 246 additions and 14 deletions

View File

@@ -68,6 +68,21 @@ const CLASSES = {
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report', FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
UNIVERSAL_SEARCH: 'universal-search',
UNIVERSAL_SEARCH_OMNIBAR: 'universal-search__omnibar',
UNIVERSAL_SEARCH_OVERLAY: 'universal-search-overlay',
UNIVERSAL_SEARCH_INPUT: 'universal-search__input',
UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS: 'universal-search-input-right-elements',
UNIVERSAL_SEARCH_FOOTER: 'universal-search__footer',
UNIVERSAL_SEARCH_ACTIONS: 'universal-search__actions',
UNIVERSAL_SEARCH_ACTION_SELECT: 'universal-search__action universal-search__action--select',
UNIVERSAL_SEARCH_ACTION_CLOSE: 'universal-search__action universal-search__action--close',
UNIVERSAL_SEARCH_ACTION_ARROWS: 'universal-search__action universal-search__action--arrows',
DIALOG_PDF_PREVIEW: 'dialog--pdf-preview-dialog',
...Classes, ...Classes,
CARD: 'card', CARD: 'card',
}; };

View File

@@ -16,7 +16,7 @@ import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs';
import DashboardBackLink from 'components/Dashboard/DashboardBackLink'; import DashboardBackLink from 'components/Dashboard/DashboardBackLink';
import { Icon, Hint, If } from 'components'; import { Icon, Hint, If } from 'components';
import withSearch from 'containers/GeneralSearch/withSearch'; import withUniversalSearch from 'containers/UniversalSearch/withUniversalSearch';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withDashboard from 'containers/Dashboard/withDashboard'; import withDashboard from 'containers/Dashboard/withDashboard';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
@@ -155,7 +155,7 @@ function DashboardTopbar({
} }
export default compose( export default compose(
withSearch, withUniversalSearch,
withDashboard(({ pageTitle, pageHint, editViewId, sidebarExpended }) => ({ withDashboard(({ pageTitle, pageHint, editViewId, sidebarExpended }) => ({
pageTitle, pageTitle,
editViewId, editViewId,

View File

@@ -5,14 +5,15 @@ import classNames from 'classnames';
export default function DialogContent(props) { export default function DialogContent(props) {
const { isLoading, children } = props; const { isLoading, children } = props;
const loadingContent = ( const loadingContent = <Spinner size={30} />;
return (
<div <div
className={classNames(Classes.DIALOG_BODY, { className={classNames(Classes.DIALOG_BODY, {
'is-loading': isLoading, 'is-loading': isLoading,
})} })}
> >
<Spinner size={30} /> {isLoading ? loadingContent : children}
</div> </div>
); );
return <div>{isLoading ? loadingContent : children}</div>;
} }

View File

@@ -14,6 +14,7 @@ import ContactDuplicateDialog from 'containers/Dialogs/ContactDuplicateDialog';
import QuickPaymentReceiveFormDialog from 'containers/Dialogs/QuickPaymentReceiveFormDialog'; import QuickPaymentReceiveFormDialog from 'containers/Dialogs/QuickPaymentReceiveFormDialog';
import QuickPaymentMadeFormDialog from 'containers/Dialogs/QuickPaymentMadeFormDialog'; import QuickPaymentMadeFormDialog from 'containers/Dialogs/QuickPaymentMadeFormDialog';
import AllocateLandedCostDialog from 'containers/Dialogs/AllocateLandedCostDialog'; import AllocateLandedCostDialog from 'containers/Dialogs/AllocateLandedCostDialog';
import InvoicePdfPreviewDialog from 'containers/Dialogs/InvoicePdfPreviewDialog';
/** /**
* Dialogs container. * Dialogs container.
@@ -34,6 +35,7 @@ export default function DialogsContainer() {
<QuickPaymentReceiveFormDialog dialogName={'quick-payment-receive'} /> <QuickPaymentReceiveFormDialog dialogName={'quick-payment-receive'} />
<QuickPaymentMadeFormDialog dialogName={'quick-payment-made'} /> <QuickPaymentMadeFormDialog dialogName={'quick-payment-made'} />
<AllocateLandedCostDialog dialogName={'allocate-landed-cost'} /> <AllocateLandedCostDialog dialogName={'allocate-landed-cost'} />
<InvoicePdfPreviewDialog dialog={'invoice-pdf-preview'} />
</div> </div>
); );
} }

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { Spinner } from '@blueprintjs/core';
/**
* Previews the pdf document of the given object url.
*/
export function PdfDocumentPreview({ url, height, width, isLoading }) {
return isLoading ? (
<Spinner size={30} />
) : (
<embed src={url} height={height} width={width} />
);
}

View File

@@ -67,6 +67,8 @@ export * from './AdvancedFilter/AdvancedFilterDropdown';
export * from './AdvancedFilter/AdvancedFilterPopover'; export * from './AdvancedFilter/AdvancedFilterPopover';
export * from './Dashboard/DashboardFilterButton'; export * from './Dashboard/DashboardFilterButton';
export * from './Dashboard/DashboardRowsHeightButton'; export * from './Dashboard/DashboardRowsHeightButton';
export * from './UniversalSearch/UniversalSearch';
export * from './PdfPreview';
const Hint = FieldHint; const Hint = FieldHint;

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { AnchorButton } from '@blueprintjs/core';
import { DialogContent, PdfDocumentPreview, T } from 'components';
import { usePdfInvoice } from 'hooks/query';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
function InvoicePdfPreviewDialogContent({
// #withDialog
closeDialog,
}) {
const { isLoading, pdfUrl } = usePdfInvoice(1);
return (
<DialogContent>
<div class="dialog__header-actions">
<AnchorButton
href={pdfUrl}
download={'invoice.pdf'}
minimal={true}
outlined={true}
>
<T id={'pdf_preview.preview.button'} />
</AnchorButton>
<AnchorButton
href={pdfUrl}
target={'__blank'}
minimal={true}
outlined={true}
>
<T id={'pdf_preview.download.button'} />
</AnchorButton>
</div>
<PdfDocumentPreview
height={760}
width={1000}
isLoading={isLoading}
url={pdfUrl}
/>
</DialogContent>
);
}
export default compose(withDialogActions)(InvoicePdfPreviewDialogContent);

View File

@@ -0,0 +1,40 @@
import React, { lazy } from 'react';
import classNames from 'classnames';
import { T, Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { CLASSES } from 'common/classes';
import { compose } from 'utils';
// Lazy loading the content.
const PdfPreviewDialogContent = lazy(() =>
import('./InvoicePdfPreviewDialogContent'),
);
/**
* Invoice PDF preview dialog.
*/
function InvoicePdfPreviewDialog({ dialogName, payload, isOpen }) {
return (
<Dialog
name={dialogName}
title={<T id={'invoice_preview.dialog.title'} />}
className={classNames(CLASSES.DIALOG_PDF_PREVIEW)}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
style={{ width: '1000px' }}
>
<DialogSuspense>
<PdfPreviewDialogContent
dialogName={dialogName}
subscriptionForm={payload}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(InvoicePdfPreviewDialog);

View File

@@ -63,4 +63,5 @@ export function useCellAutoFocus(ref, autoFocus, columnId, rowIndex) {
return ref; return ref;
} }
export * from './useRequestPdf';
export { useAsync, useAutofocus }; export { useAsync, useAutofocus };

View File

@@ -2,6 +2,7 @@ import { useQueryClient, useMutation } from 'react-query';
import { useRequestQuery } from '../useQueryRequest'; import { useRequestQuery } from '../useQueryRequest';
import { transformPagination } from 'utils'; import { transformPagination } from 'utils';
import useApiRequest from '../useRequest'; import useApiRequest from '../useRequest';
import { useRequestPdf } from '../useRequestPdf';
import t from './types'; import t from './types';
// Common invalidate queries. // Common invalidate queries.
@@ -145,10 +146,10 @@ export function useDeliverInvoice(props) {
* Retrieve the sale invoice details. * Retrieve the sale invoice details.
* @param {number} invoiceId - Invoice id. * @param {number} invoiceId - Invoice id.
*/ */
export function useInvoice(invoiceId, props) { export function useInvoice(invoiceId, props, requestProps) {
return useRequestQuery( return useRequestQuery(
[t.SALE_INVOICE, invoiceId], [t.SALE_INVOICE, invoiceId],
{ method: 'get', url: `sales/invoices/${invoiceId}` }, { method: 'get', url: `sales/invoices/${invoiceId}`, ...requestProps },
{ {
select: (res) => res.data.sale_invoice, select: (res) => res.data.sale_invoice,
defaultData: {}, defaultData: {},
@@ -157,6 +158,13 @@ export function useInvoice(invoiceId, props) {
); );
} }
/**
* Retrieve the invoice pdf document data.
*/
export function usePdfInvoice(invoiceId) {
return useRequestPdf(`/sales/invoices/${invoiceId}`);
}
/** /**
* Retrieve due invoices of the given customer id. * Retrieve due invoices of the given customer id.
* @param {number} customerId - Customer id. * @param {number} customerId - Customer id.

View File

@@ -62,7 +62,7 @@ export default function useApiRequest() {
return instance; return instance;
}, [token, organizationId, setGlobalErrors, setLogout]); }, [token, organizationId, setGlobalErrors, setLogout]);
return { return React.useMemo(() => ({
http, http,
get(resource, params) { get(resource, params) {
@@ -84,5 +84,5 @@ export default function useApiRequest() {
delete(resource, params) { delete(resource, params) {
return http.delete(`/api/${resource}`, params); return http.delete(`/api/${resource}`, params);
}, },
}; }), [http]);
} }

View File

@@ -0,0 +1,38 @@
import React from 'react';
import useApiRequest from './useRequest';
export const useRequestPdf = (url) => {
const apiRequest = useApiRequest();
const [isLoading, setIsLoading] = React.useState(false);
const [isLoaded, setIsLoaded] = React.useState(false);
const [pdfUrl, setPdfUrl] = React.useState('');
const [response, setResponse] = React.useState(null);
React.useEffect(() => {
setIsLoading(true);
apiRequest
.get(url, {
headers: { accept: 'application/pdf' },
responseType: 'blob',
})
.then((response) => {
// Create a Blob from the PDF Stream.
const file = new Blob([response.data], { type: 'application/pdf' });
// Build a URL from the file
const fileURL = URL.createObjectURL(file);
setPdfUrl(fileURL);
setIsLoading(false);
setIsLoaded(true);
setResponse(response);
});
}, []);
return {
isLoading,
isLoaded,
pdfUrl,
response,
};
};

View File

@@ -1204,6 +1204,21 @@
"the_contact_has_been_activated_successfully": "تم تفعيل جهة اتصال بنجاح.", "the_contact_has_been_activated_successfully": "تم تفعيل جهة اتصال بنجاح.",
"the_contact_has_been_inactivated_successfully": "تم إلغاء تنشيط جهة اتصال بنجاح.", "the_contact_has_been_inactivated_successfully": "تم إلغاء تنشيط جهة اتصال بنجاح.",
"are_sure_to_inactive_this_contact": "هل أنت متأكد أنك تريد إلغاء تنشيط جهة اتصال؟ ستكون قادرًا على تنشيطه لاحقًا", "are_sure_to_inactive_this_contact": "هل أنت متأكد أنك تريد إلغاء تنشيط جهة اتصال؟ ستكون قادرًا على تنشيطه لاحقًا",
"are_sure_to_activate_this_contact": "هل أنت متأكد أنك تريد تفعيل جهة اتصال؟ ستتمكن من تعطيله لاحقًا" "are_sure_to_activate_this_contact": "هل أنت متأكد أنك تريد تفعيل جهة اتصال؟ ستتمكن من تعطيله لاحقًا",
"publish_adjustment": "Publish adjustment",
"inactivate_customer": "Inactivate customer",
"activate_customer": "Activate customer",
"filter.all_filters_must_match": "Atleast one filter must match",
"filter.atleast_one_filter_must_match": "Atleast one filter must match",
"filter.when": "When",
"universal_search.placeholder": "Search...",
"universal_search.enter_text": "To select",
"universal_search.close_text": "To close",
"universal_seach.navigate_text": "To navigate",
"pdf_preview.dialog.title": "PDF Preview",
"pdf_preview.download.button": "Download",
"pdf_preview.preview.button": "Preview",
"invoice_preview.dialog.title": "Invoice PDF Preview"
} }

View File

@@ -1197,5 +1197,13 @@
"activate_customer": "Activate customer", "activate_customer": "Activate customer",
"filter.all_filters_must_match": "Atleast one filter must match", "filter.all_filters_must_match": "Atleast one filter must match",
"filter.atleast_one_filter_must_match": "Atleast one filter must match", "filter.atleast_one_filter_must_match": "Atleast one filter must match",
"filter.when": "When" "filter.when": "When",
"universal_search.placeholder": "Search...",
"universal_search.enter_text": "To select",
"universal_search.close_text": "To close",
"universal_seach.navigate_text": "To navigate",
"pdf_preview.dialog.title": "PDF Preview",
"pdf_preview.download.button": "Download",
"pdf_preview.preview.button": "Preview",
"invoice_preview.dialog.title": "Invoice PDF Preview"
} }

View File

@@ -29,6 +29,7 @@
@import 'components/Overlay'; @import 'components/Overlay';
@import 'components/Menu'; @import 'components/Menu';
@import 'components/SidebarOverlay'; @import 'components/SidebarOverlay';
@import 'components/UniversalSearch';
// Pages // Pages
@import 'pages/view-form'; @import 'pages/view-form';
@@ -201,12 +202,52 @@ html[lang^="ar"] {
} }
} }
.bp3-popover2{ .bp3-popover2 {
box-shadow: 0 0 0; box-shadow: 0 0 0;
} }
.bp3-tooltip2 .bp3-popover2-arrow:before {
.bp3-tooltip2 .bp3-popover2-arrow:before{
box-shadow: 0 0 0; box-shadow: 0 0 0;
} }
.dialog--pdf-preview-dialog {
width: 1000px;
max-height: 800px;
margin: 10px 0;
overflow: hidden;
align-self: flex-start;
padding-bottom: 0;
position: relative;
.dialog__header-actions{
position: absolute;
right: 50px;
top: 0;
z-index: 9999999;
margin: 6px;
.bp3-button + .bp3-button{
margin-left: 8px;
}
.bp3-button{
border-color: rgba(0, 0, 0, 0.25);
color: rgb(25, 32, 37);
min-height: 30px;
padding-left: 14px;
padding-right: 14px;
}
}
.bp3-dialog {
&-body {
margin: 0;
> .bp3-spinner {
margin: 20px 0;
}
}
}
}