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