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',
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,
CARD: 'card',
};

View File

@@ -16,7 +16,7 @@ import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs';
import DashboardBackLink from 'components/Dashboard/DashboardBackLink';
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 withDashboard from 'containers/Dashboard/withDashboard';
import withSettings from 'containers/Settings/withSettings';
@@ -155,7 +155,7 @@ function DashboardTopbar({
}
export default compose(
withSearch,
withUniversalSearch,
withDashboard(({ pageTitle, pageHint, editViewId, sidebarExpended }) => ({
pageTitle,
editViewId,

View File

@@ -5,14 +5,15 @@ import classNames from 'classnames';
export default function DialogContent(props) {
const { isLoading, children } = props;
const loadingContent = (
const loadingContent = <Spinner size={30} />;
return (
<div
className={classNames(Classes.DIALOG_BODY, {
'is-loading': isLoading,
})}
>
<Spinner size={30} />
{isLoading ? loadingContent : children}
</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 QuickPaymentMadeFormDialog from 'containers/Dialogs/QuickPaymentMadeFormDialog';
import AllocateLandedCostDialog from 'containers/Dialogs/AllocateLandedCostDialog';
import InvoicePdfPreviewDialog from 'containers/Dialogs/InvoicePdfPreviewDialog';
/**
* Dialogs container.
@@ -34,6 +35,7 @@ export default function DialogsContainer() {
<QuickPaymentReceiveFormDialog dialogName={'quick-payment-receive'} />
<QuickPaymentMadeFormDialog dialogName={'quick-payment-made'} />
<AllocateLandedCostDialog dialogName={'allocate-landed-cost'} />
<InvoicePdfPreviewDialog dialog={'invoice-pdf-preview'} />
</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 './Dashboard/DashboardFilterButton';
export * from './Dashboard/DashboardRowsHeightButton';
export * from './UniversalSearch/UniversalSearch';
export * from './PdfPreview';
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;
}
export * from './useRequestPdf';
export { useAsync, useAutofocus };

View File

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

View File

@@ -62,7 +62,7 @@ export default function useApiRequest() {
return instance;
}, [token, organizationId, setGlobalErrors, setLogout]);
return {
return React.useMemo(() => ({
http,
get(resource, params) {
@@ -84,5 +84,5 @@ export default function useApiRequest() {
delete(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_inactivated_successfully": "تم إلغاء تنشيط جهة اتصال بنجاح.",
"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",
"filter.all_filters_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/Menu';
@import 'components/SidebarOverlay';
@import 'components/UniversalSearch';
// Pages
@import 'pages/view-form';
@@ -201,12 +202,52 @@ html[lang^="ar"] {
}
}
.bp3-popover2{
.bp3-popover2 {
box-shadow: 0 0 0;
}
.bp3-tooltip2 .bp3-popover2-arrow:before{
.bp3-tooltip2 .bp3-popover2-arrow:before {
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;
}
}
}
}