mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
feat: invoice pdf preview dialog.
This commit is contained in:
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
13
client/src/components/PdfPreview/index.js
Normal file
13
client/src/components/PdfPreview/index.js
Normal 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} />
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -63,4 +63,5 @@ export function useCellAutoFocus(ref, autoFocus, columnId, rowIndex) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
export * from './useRequestPdf';
|
||||
export { useAsync, useAutofocus };
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
38
client/src/hooks/useRequestPdf.js
Normal file
38
client/src/hooks/useRequestPdf.js
Normal 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,
|
||||
};
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user