diff --git a/client/src/common/classes.js b/client/src/common/classes.js index 09b8ac38f..52928fa1e 100644 --- a/client/src/common/classes.js +++ b/client/src/common/classes.js @@ -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', }; diff --git a/client/src/components/Dashboard/DashboardTopbar.js b/client/src/components/Dashboard/DashboardTopbar.js index 2422538c6..bba610b9b 100644 --- a/client/src/components/Dashboard/DashboardTopbar.js +++ b/client/src/components/Dashboard/DashboardTopbar.js @@ -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, diff --git a/client/src/components/Dialog/DialogContent.js b/client/src/components/Dialog/DialogContent.js index 56eab54dd..514521cd1 100644 --- a/client/src/components/Dialog/DialogContent.js +++ b/client/src/components/Dialog/DialogContent.js @@ -5,14 +5,15 @@ import classNames from 'classnames'; export default function DialogContent(props) { const { isLoading, children } = props; - const loadingContent = ( + const loadingContent = ; + + return (
- + {isLoading ? loadingContent : children}
); - return
{isLoading ? loadingContent : children}
; } diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js index 203a769b6..a9aa24ff0 100644 --- a/client/src/components/DialogsContainer.js +++ b/client/src/components/DialogsContainer.js @@ -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() { + ); } diff --git a/client/src/components/PdfPreview/index.js b/client/src/components/PdfPreview/index.js new file mode 100644 index 000000000..c6279b667 --- /dev/null +++ b/client/src/components/PdfPreview/index.js @@ -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 ? ( + + ) : ( + + ); +} diff --git a/client/src/components/index.js b/client/src/components/index.js index 9ce14e0d5..3718c0ae8 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -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; diff --git a/client/src/containers/Dialogs/InvoicePdfPreviewDialog/InvoicePdfPreviewDialogContent.js b/client/src/containers/Dialogs/InvoicePdfPreviewDialog/InvoicePdfPreviewDialogContent.js new file mode 100644 index 000000000..3dfe4e3fa --- /dev/null +++ b/client/src/containers/Dialogs/InvoicePdfPreviewDialog/InvoicePdfPreviewDialogContent.js @@ -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 ( + +
+ + + + + + + +
+ + +
+ ); +} + +export default compose(withDialogActions)(InvoicePdfPreviewDialogContent); \ No newline at end of file diff --git a/client/src/containers/Dialogs/InvoicePdfPreviewDialog/index.js b/client/src/containers/Dialogs/InvoicePdfPreviewDialog/index.js new file mode 100644 index 000000000..b989c92b0 --- /dev/null +++ b/client/src/containers/Dialogs/InvoicePdfPreviewDialog/index.js @@ -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 ( + } + className={classNames(CLASSES.DIALOG_PDF_PREVIEW)} + autoFocus={true} + canEscapeKeyClose={true} + isOpen={isOpen} + style={{ width: '1000px' }} + > + + + + + ); +} + +export default compose(withDialogRedux())(InvoicePdfPreviewDialog); diff --git a/client/src/hooks/index.js b/client/src/hooks/index.js index e1f705578..f48c66afa 100644 --- a/client/src/hooks/index.js +++ b/client/src/hooks/index.js @@ -63,4 +63,5 @@ export function useCellAutoFocus(ref, autoFocus, columnId, rowIndex) { return ref; } +export * from './useRequestPdf'; export { useAsync, useAutofocus }; diff --git a/client/src/hooks/query/invoices.js b/client/src/hooks/query/invoices.js index 93a89ed8e..4bf955cf0 100644 --- a/client/src/hooks/query/invoices.js +++ b/client/src/hooks/query/invoices.js @@ -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. diff --git a/client/src/hooks/useRequest.js b/client/src/hooks/useRequest.js index 6f15ea272..634a826a8 100644 --- a/client/src/hooks/useRequest.js +++ b/client/src/hooks/useRequest.js @@ -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]); } diff --git a/client/src/hooks/useRequestPdf.js b/client/src/hooks/useRequestPdf.js new file mode 100644 index 000000000..a82e9ccba --- /dev/null +++ b/client/src/hooks/useRequestPdf.js @@ -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, + }; +}; diff --git a/client/src/lang/ar-ly/index.json b/client/src/lang/ar-ly/index.json index e031387fc..55eb3d5f7 100644 --- a/client/src/lang/ar-ly/index.json +++ b/client/src/lang/ar-ly/index.json @@ -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" } diff --git a/client/src/lang/en/index.json b/client/src/lang/en/index.json index a55aed880..112ab2089 100644 --- a/client/src/lang/en/index.json +++ b/client/src/lang/en/index.json @@ -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" } \ No newline at end of file diff --git a/client/src/style/App.scss b/client/src/style/App.scss index e5b926dee..572c3aa34 100644 --- a/client/src/style/App.scss +++ b/client/src/style/App.scss @@ -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; + } + } + } + +} \ No newline at end of file