diff --git a/src/common/classes.js b/src/common/classes.js
index f20587caa..bea33a718 100644
--- a/src/common/classes.js
+++ b/src/common/classes.js
@@ -66,6 +66,7 @@ const CLASSES = {
PREFERENCES_PAGE_INSIDE_CONTENT_USERS: 'preferences-page__inside-content--users',
PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES: 'preferences-page__inside-content--currencies',
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
+ PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION: 'preferences-page__inside-content--sms-integration',
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
diff --git a/src/components/Button/ButtonLink.js b/src/components/Button/ButtonLink.js
index 9b9cb7b5b..655cdc615 100644
--- a/src/components/Button/ButtonLink.js
+++ b/src/components/Button/ButtonLink.js
@@ -1,23 +1,13 @@
import styled from 'styled-components';
-import { Button } from '@blueprintjs/core';
-export const ButtonLink = styled(Button)`
- line-height: inherit;
+export const ButtonLink = styled.button`
+ color: #0052cc;
+ border: 0;
+ background: transparent;
+ cursor: pointer;
- &.bp3-small {
- min-height: auto;
- min-width: auto;
- padding: 0;
- }
- &:not([class*='bp3-intent-']) {
- &,
- &:hover {
- color: #0052cc;
- background: transparent;
- }
-
- &:hover {
- text-decoration: underline;
- }
+ &:hover,
+ &:active {
+ text-decoration: underline;
}
`;
diff --git a/src/components/DataTableCells/SwitchFieldCell.js b/src/components/DataTableCells/SwitchFieldCell.js
new file mode 100644
index 000000000..a25e96ae4
--- /dev/null
+++ b/src/components/DataTableCells/SwitchFieldCell.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Classes, Switch, FormGroup, Intent } from '@blueprintjs/core';
+
+import { safeInvoke } from 'utils';
+
+/**
+ * Switch editable cell.
+ */
+const SwitchEditableCell = ({
+ row: { index, original },
+ column: { id, switchProps, onSwitchChange },
+ cell: { value: initialValue },
+ payload,
+}) => {
+ const [value, setValue] = React.useState(initialValue);
+
+ // Handle the switch change.
+ const onChange = (e) => {
+ const newValue = e.target.checked;
+
+ setValue(newValue);
+
+ safeInvoke(payload.updateData, index, id, newValue);
+ safeInvoke(onSwitchChange, e, newValue, original);
+ };
+
+ React.useEffect(() => {
+ setValue(initialValue);
+ }, [initialValue]);
+
+ const error = payload.errors?.[index]?.[id];
+
+ return (
+
+
+
+ );
+};
+
+export default SwitchEditableCell;
\ No newline at end of file
diff --git a/src/components/DataTableCells/TextAreaCell.js b/src/components/DataTableCells/TextAreaCell.js
new file mode 100644
index 000000000..c8ff97a0c
--- /dev/null
+++ b/src/components/DataTableCells/TextAreaCell.js
@@ -0,0 +1,42 @@
+import React, { useState, useEffect } from 'react';
+import classNames from 'classnames';
+import { Classes, TextArea, FormGroup, Intent } from '@blueprintjs/core';
+
+const TextAreaEditableCell = ({
+ row: { index },
+ column: { id },
+ cell: { value: initialValue },
+ payload,
+}) => {
+ const [value, setValue] = useState(initialValue);
+
+ const onChange = (e) => {
+ setValue(e.target.value);
+ };
+ const onBlur = () => {
+ payload.updateData(index, id, value);
+ };
+ useEffect(() => {
+ setValue(initialValue);
+ }, [initialValue]);
+
+ const error = payload.errors?.[index]?.[id];
+
+ return (
+
+
+
+ );
+};
+
+export default TextAreaEditableCell;
diff --git a/src/components/DataTableCells/index.js b/src/components/DataTableCells/index.js
index 2a0688ad2..fed349eed 100644
--- a/src/components/DataTableCells/index.js
+++ b/src/components/DataTableCells/index.js
@@ -6,7 +6,9 @@ import ItemsListCell from './ItemsListCell';
import PercentFieldCell from './PercentFieldCell';
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
import NumericInputCell from './NumericInputCell';
-import CheckBoxFieldCell from './CheckBoxFieldCell'
+import CheckBoxFieldCell from './CheckBoxFieldCell';
+import SwitchFieldCell from './SwitchFieldCell';
+import TextAreaCell from './TextAreaCell';
export {
AccountsListFieldCell,
@@ -18,5 +20,7 @@ export {
DivFieldCell,
EmptyDiv,
NumericInputCell,
- CheckBoxFieldCell
+ CheckBoxFieldCell,
+ SwitchFieldCell,
+ TextAreaCell,
};
diff --git a/src/components/Dialog/Dialog.js b/src/components/Dialog/Dialog.js
index 95e1fdcbf..c5f793251 100644
--- a/src/components/Dialog/Dialog.js
+++ b/src/components/Dialog/Dialog.js
@@ -9,16 +9,16 @@ function DialogComponent(props) {
const { name, children, closeDialog, onClose } = props;
const handleClose = (event) => {
- closeDialog(name)
+ closeDialog(name);
onClose && onClose(event);
};
return (
);
}
-export default compose(
- withDialogActions,
-)(DialogComponent);
\ No newline at end of file
+const DialogRoot = compose(withDialogActions)(DialogComponent);
+
+export { DialogRoot as Dialog };
diff --git a/src/components/Dialog/DialogContent.js b/src/components/Dialog/DialogContent.js
index 265182bc5..cb438f499 100644
--- a/src/components/Dialog/DialogContent.js
+++ b/src/components/Dialog/DialogContent.js
@@ -2,7 +2,7 @@ import React from 'react';
import { Spinner, Classes } from '@blueprintjs/core';
import classNames from 'classnames';
-export default function DialogContent(props) {
+export function DialogContent(props) {
const { isLoading, children } = props;
const loadingContent = (
diff --git a/src/components/Dialog/DialogFooterActions.js b/src/components/Dialog/DialogFooterActions.js
new file mode 100644
index 000000000..91c856e9a
--- /dev/null
+++ b/src/components/Dialog/DialogFooterActions.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import styled from 'styled-components';
+import { Classes } from '@blueprintjs/core';
+
+export function DialogFooterActions({ alignment = 'right', children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+const DialogFooterActionsRoot = styled.div`
+ margin-left: -10px;
+ margin-right: -10px;
+ justify-content: ${(props) =>
+ props.alignment === 'right' ? 'flex-end' : 'flex-start'};
+
+ .bp3-button {
+ margin-left: 10px;
+ margin-left: 10px;
+ }
+`;
diff --git a/src/components/Dialog/DialogSuspense.js b/src/components/Dialog/DialogSuspense.js
index 6513d28c5..56ed625b9 100644
--- a/src/components/Dialog/DialogSuspense.js
+++ b/src/components/Dialog/DialogSuspense.js
@@ -5,7 +5,7 @@ function LoadingContent() {
return (
);
}
-export default function DialogSuspense({
+export function DialogSuspense({
children
}) {
return (
diff --git a/src/components/Dialog/index.js b/src/components/Dialog/index.js
new file mode 100644
index 000000000..c7c0982fb
--- /dev/null
+++ b/src/components/Dialog/index.js
@@ -0,0 +1,6 @@
+
+
+export * from './Dialog';
+export * from './DialogFooterActions';
+export * from './DialogSuspense';
+export * from './DialogContent';
\ No newline at end of file
diff --git a/src/components/DialogsContainer.js b/src/components/DialogsContainer.js
index 1a31006dc..db9f883c2 100644
--- a/src/components/DialogsContainer.js
+++ b/src/components/DialogsContainer.js
@@ -20,6 +20,11 @@ import ReceiptPdfPreviewDialog from '../containers/Dialogs/ReceiptPdfPreviewDial
import MoneyInDialog from '../containers/Dialogs/MoneyInDialog';
import MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
import BadDebtDialog from '../containers/Dialogs/BadDebtDialog';
+import NotifyInvoiceViaSMSDialog from '../containers/Dialogs/NotifyInvoiceViaSMSDialog';
+import NotifyReceiptViaSMSDialog from '../containers/Dialogs/NotifyReceiptViaSMSDialog';
+import NotifyEstimateViaSMSDialog from '../containers/Dialogs/NotifyEstimateViaSMSDialog';
+import NotifyPaymentReceiveViaSMSDialog from '../containers/Dialogs/NotifyPaymentReceiveViaSMSDialog'
+import SMSMessageDialog from '../containers/Dialogs/SMSMessageDialog';
/**
* Dialogs container.
@@ -45,7 +50,14 @@ export default function DialogsContainer() {
+
+
+
+
+
+
+
);
}
diff --git a/src/components/MoreMenutItems.js b/src/components/MoreMenutItems.js
new file mode 100644
index 000000000..2201a7206
--- /dev/null
+++ b/src/components/MoreMenutItems.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import {
+ Button,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+ MenuItem,
+ Menu,
+} from '@blueprintjs/core';
+
+import { Icon, FormattedMessage as T } from 'components';
+
+function MoreMenuItems({ payload: { onNotifyViaSMS } }) {
+ return (
+
+ }
+ />
+
+ }
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ modifiers={{
+ offset: { offset: '0, 4' },
+ }}
+ >
+ } minimal={true} />
+
+ );
+}
+
+export default MoreMenuItems;
diff --git a/src/components/MoreVertMenutItems.js b/src/components/MoreVertMenutItems.js
deleted file mode 100644
index 9282f33c1..000000000
--- a/src/components/MoreVertMenutItems.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import {
- Button,
- PopoverInteractionKind,
- MenuItem,
- Position,
-} from '@blueprintjs/core';
-
-import { Select } from '@blueprintjs/select';
-import { Icon } from 'components';
-
-function MoreVertMenutItems({ text, items, onItemSelect, buttonProps }) {
- // Menu items renderer.
- const itemsRenderer = (item, { handleClick, modifiers, query }) => (
-
- );
- const handleMenuSelect = (type) => {
- onItemSelect && onItemSelect(type);
- };
-
- return (
-
- );
-}
-
-export default MoreVertMenutItems;
diff --git a/src/components/SMSPreview/index.js b/src/components/SMSPreview/index.js
new file mode 100644
index 000000000..0b53aaf4b
--- /dev/null
+++ b/src/components/SMSPreview/index.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import styled from 'styled-components';
+
+import { Icon } from 'components';
+
+/**
+ * SMS Message preview.
+ */
+export function SMSMessagePreview({
+ message,
+ iconWidth = '265px',
+ iconHeight = '287px',
+ iconColor = '#adadad',
+}) {
+ return (
+
+
+ {message}
+
+ );
+}
+
+const SMSMessageText = styled.div`
+ position: absolute;
+ top: 60px;
+ padding: 12px;
+ color: #fff;
+ border-radius: 12px;
+ margin-left: 12px;
+ margin-right: 12px;
+ word-break: break-word;
+ background: #2fa2e4;
+ font-size: 13px;
+ line-height: 1.6;
+`;
+
+const SMSMessagePreviewBase = styled.div`
+ position: relative;
+ width: 265px;
+ margin: 0 auto;
+`;
diff --git a/src/components/index.js b/src/components/index.js
index aa156307e..b8ca111f4 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -23,9 +23,6 @@ import AccountsSelectList from './AccountsSelectList';
import AccountsTypesSelect from './AccountsTypesSelect';
import LoadingIndicator from './LoadingIndicator';
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
-import Dialog from './Dialog/Dialog';
-import DialogContent from './Dialog/DialogContent';
-import DialogSuspense from './Dialog/DialogSuspense';
import InputPrependButton from './Forms/InputPrependButton';
import CategoriesSelectList from './CategoriesSelectList';
import Row from './Grid/Row';
@@ -61,8 +58,9 @@ import Card from './Card';
import AvaterCell from './AvaterCell';
import { ItemsMultiSelect } from './Items';
-import MoreVertMenutItems from './MoreVertMenutItems';
+import MoreMenuItems from './MoreMenutItems';
+export * from './Dialog';
export * from './Menu';
export * from './AdvancedFilter/AdvancedFilterDropdown';
export * from './AdvancedFilter/AdvancedFilterPopover';
@@ -83,10 +81,11 @@ export * from './MultiSelectTaggable';
export * from './Utils/FormatNumber';
export * from './Utils/FormatDate';
export * from './BankAccounts';
-export * from './IntersectionObserver'
+export * from './IntersectionObserver';
export * from './Datatable/CellForceWidth';
export * from './Button';
export * from './IntersectionObserver';
+export * from './SMSPreview';
const Hint = FieldHint;
@@ -120,9 +119,6 @@ export {
LoadingIndicator,
DashboardActionViewsList,
AppToaster,
- Dialog,
- DialogContent,
- DialogSuspense,
InputPrependButton,
CategoriesSelectList,
Col,
@@ -158,5 +154,5 @@ export {
ItemsMultiSelect,
Card,
AvaterCell,
- MoreVertMenutItems,
+ MoreMenuItems,
};
diff --git a/src/config/preferencesMenu.js b/src/config/preferencesMenu.js
index 3e1b981dd..031dfe0e1 100644
--- a/src/config/preferencesMenu.js
+++ b/src/config/preferencesMenu.js
@@ -1,29 +1,34 @@
-import React from 'react'
-import { FormattedMessage as T } from 'components';
+import React from 'react';
+import { FormattedMessage as T } from 'components';
export default [
{
- text: ,
+ text: ,
disabled: false,
href: '/preferences/general',
},
{
- text: ,
+ text: ,
href: '/preferences/users',
},
{
- text: ,
-
+ text: ,
+
href: '/preferences/currencies',
},
{
- text: ,
+ text: ,
disabled: false,
href: '/preferences/accountant',
},
{
- text: ,
+ text: ,
disabled: false,
href: '/preferences/items',
},
+ {
+ text: ,
+ disabled: false,
+ href: '/preferences/sms-message',
+ },
];
diff --git a/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSDialogContent.js b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSDialogContent.js
new file mode 100644
index 000000000..6384dcc1e
--- /dev/null
+++ b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSDialogContent.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { NotifyEstimateViaSMSFormProvider } from './NotifyEstimateViaSMSFormProvider';
+import NotifyEstimateViaSMSForm from './NotifyEstimateViaSMSForm';
+
+export default function NotifyEstimateViaSMSDialogContent({
+ // #ownProps
+ dialogName,
+ estimate,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSForm.js b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSForm.js
new file mode 100644
index 000000000..a7da22dd0
--- /dev/null
+++ b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSForm.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { Intent } from '@blueprintjs/core';
+import { AppToaster } from 'components';
+
+import NotifyViaSMSForm from '../../NotifyViaSMS/NotifyViaSMSForm';
+import { useEstimateViaSMSContext } from './NotifyEstimateViaSMSFormProvider';
+import { transformErrors } from '../../../containers/NotifyViaSMS/utils';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+const notificationType = {
+ key: 'sale-estimate-details',
+ label: 'Sale estimate details',
+};
+
+function NotifyEstimateViaSMSForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const {
+ estimateId,
+ dialogName,
+ estimateSMSDetail,
+ createNotifyEstimateBySMSMutate,
+ } = useEstimateViaSMSContext();
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ setSubmitting(true);
+
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get('notify_estimate_via_sms.dialog.success_message'),
+ intent: Intent.SUCCESS,
+ });
+ closeDialog(dialogName);
+ setSubmitting(false);
+ };
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ if (errors) {
+ transformErrors(errors, { setErrors });
+ }
+ setSubmitting(false);
+ };
+ createNotifyEstimateBySMSMutate([estimateId, values])
+ .then(onSuccess)
+ .catch(onError);
+ };
+
+ const initialValues = {
+ ...estimateSMSDetail,
+ };
+ // Handle the form cancel.
+ const handleFormCancel = () => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(NotifyEstimateViaSMSForm);
diff --git a/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSFormProvider.js b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSFormProvider.js
new file mode 100644
index 000000000..f913bdc23
--- /dev/null
+++ b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/NotifyEstimateViaSMSFormProvider.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import {
+ useEstimateSMSDetail,
+ useCreateNotifyEstimateBySMS,
+} from 'hooks/query';
+
+const NotifyEstimateViaSMSContext = React.createContext();
+
+function NotifyEstimateViaSMSFormProvider({
+ estimateId,
+ dialogName,
+ ...props
+}) {
+ const { data: estimateSMSDetail, isLoading: isEstimateSMSDetailLoading } =
+ useEstimateSMSDetail(estimateId, {
+ enabled: !!estimateId,
+ });
+
+ // Create notfiy estimate by sms mutations.
+ const { mutateAsync: createNotifyEstimateBySMSMutate } =
+ useCreateNotifyEstimateBySMS();
+
+ // State provider.
+ const provider = {
+ estimateId,
+ dialogName,
+ estimateSMSDetail,
+ createNotifyEstimateBySMSMutate,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useEstimateViaSMSContext = () =>
+ React.useContext(NotifyEstimateViaSMSContext);
+
+export { NotifyEstimateViaSMSFormProvider, useEstimateViaSMSContext };
diff --git a/src/containers/Dialogs/NotifyEstimateViaSMSDialog/index.js b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/index.js
new file mode 100644
index 000000000..a935e5852
--- /dev/null
+++ b/src/containers/Dialogs/NotifyEstimateViaSMSDialog/index.js
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import { FormattedMessage as T } from 'components';
+import { Dialog, DialogSuspense } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose } from 'utils';
+
+const NotifyEstimateViaSMSDialogContent = React.lazy(() =>
+ import('./NotifyEstimateViaSMSDialogContent'),
+);
+
+function NotifyEstimateViaSMSDialog({
+ dialogName,
+ payload: { estimateId },
+ isOpen,
+}) {
+ return (
+ }
+ isOpen={isOpen}
+ canEscapeJeyClose={true}
+ autoFocus={true}
+ className={'dialog--notify-vis-sms'}
+ >
+
+
+
+
+ );
+}
+
+export default compose(withDialogRedux())(NotifyEstimateViaSMSDialog);
diff --git a/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSDialogContent.js b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSDialogContent.js
new file mode 100644
index 000000000..349312e89
--- /dev/null
+++ b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSDialogContent.js
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import { NotifyInvoiceViaSMSFormProvider } from './NotifyInvoiceViaSMSFormProvider';
+import NotifyInvoiceViaSMSForm from './NotifyInvoiceViaSMSForm';
+
+export default function NotifyInvoiceViaSMSDialogContent({
+ // #ownProps
+ dialogName,
+ invoiceId,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSForm.js b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSForm.js
new file mode 100644
index 000000000..4c703c347
--- /dev/null
+++ b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSForm.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { pick } from 'lodash';
+import { Intent } from '@blueprintjs/core';
+import { AppToaster } from 'components';
+
+import NotifyViaSMSForm from '../../NotifyViaSMS/NotifyViaSMSForm';
+import { useNotifyInvoiceViaSMSContext } from './NotifyInvoiceViaSMSFormProvider';
+import { transformErrors } from '../../../containers/NotifyViaSMS/utils';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+const transformFormValuesToRequest = (values) => {
+ return pick(values, ['notification_key']);
+};
+
+/**
+ * Notify Invoice Via SMS Form.
+ */
+function NotifyInvoiceViaSMSForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const {
+ createNotifyInvoiceBySMSMutate,
+ invoiceId,
+ invoiceSMSDetail,
+ dialogName,
+ notificationType,
+ setNotificationType,
+ } = useNotifyInvoiceViaSMSContext();
+
+ const [calloutCode, setCalloutCode] = React.useState([]);
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ setSubmitting(true);
+
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get('notify_invoice_via_sms.dialog.success_message'),
+ intent: Intent.SUCCESS,
+ });
+ setSubmitting(false);
+ closeDialog(dialogName);
+ };
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ if (errors) {
+ transformErrors(errors, { setErrors, setCalloutCode });
+ }
+ setSubmitting(false);
+ };
+ // Transformes the form values to request.
+ const requestValues = transformFormValuesToRequest(values);
+
+ // Submits invoice SMS notification.
+ createNotifyInvoiceBySMSMutate([invoiceId, requestValues])
+ .then(onSuccess)
+ .catch(onError);
+ };
+ // Handle the form cancel.
+ const handleFormCancel = React.useCallback(() => {
+ closeDialog(dialogName);
+ }, [closeDialog, dialogName]);
+
+ const initialValues = {
+ notification_key: notificationType,
+ ...invoiceSMSDetail,
+ };
+ // Handle form values change.
+ const handleValuesChange = (values) => {
+ if (values.notification_key !== notificationType) {
+ setNotificationType(values.notification_key);
+ }
+ };
+ // Momerize the notification types.
+ const notificationTypes = React.useMemo(
+ () => [
+ { key: 'details', label: 'Invoice details' },
+ { key: 'reminder', label: 'Invoice reminder' },
+ ],
+ [],
+ );
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(NotifyInvoiceViaSMSForm);
diff --git a/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSFormProvider.js b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSFormProvider.js
new file mode 100644
index 000000000..2d1bec964
--- /dev/null
+++ b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/NotifyInvoiceViaSMSFormProvider.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import { useCreateNotifyInvoiceBySMS, useInvoiceSMSDetail } from 'hooks/query';
+
+const NotifyInvoiceViaSMSContext = React.createContext();
+
+/**
+ * Invoice SMS notification provider.
+ */
+function NotifyInvoiceViaSMSFormProvider({ invoiceId, dialogName, ...props }) {
+ const [notificationType, setNotificationType] = React.useState('details');
+
+ // Retrieve the invoice sms notification message details.
+ const { data: invoiceSMSDetail, isLoading: isInvoiceSMSDetailLoading } =
+ useInvoiceSMSDetail(
+ invoiceId,
+ {
+ notification_key: notificationType,
+ },
+ {
+ enabled: !!invoiceId,
+ keepPreviousData: true,
+ },
+ );
+ // Create notfiy invoice by sms mutations.
+ const { mutateAsync: createNotifyInvoiceBySMSMutate } =
+ useCreateNotifyInvoiceBySMS();
+
+ // State provider.
+ const provider = {
+ invoiceId,
+ invoiceSMSDetail,
+ dialogName,
+ createNotifyInvoiceBySMSMutate,
+
+ notificationType,
+ setNotificationType,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useNotifyInvoiceViaSMSContext = () =>
+ React.useContext(NotifyInvoiceViaSMSContext);
+
+export { NotifyInvoiceViaSMSFormProvider, useNotifyInvoiceViaSMSContext };
diff --git a/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/index.js b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/index.js
new file mode 100644
index 000000000..91f0c75e6
--- /dev/null
+++ b/src/containers/Dialogs/NotifyInvoiceViaSMSDialog/index.js
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import { FormattedMessage as T } from 'components';
+import { Dialog, DialogSuspense } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose } from 'utils';
+
+const NotifyInvoiceViaSMSDialogContent = React.lazy(() =>
+ import('./NotifyInvoiceViaSMSDialogContent'),
+);
+
+function NotifyInvoiceViaSMSDialog({
+ dialogName,
+ payload: { invoiceId },
+ isOpen,
+}) {
+ return (
+ }
+ isOpen={isOpen}
+ canEscapeJeyClose={true}
+ autoFocus={true}
+ className={'dialog--notify-vis-sms'}
+ >
+
+
+
+
+ );
+}
+
+export default compose(withDialogRedux())(NotifyInvoiceViaSMSDialog);
diff --git a/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaFormProvider.js b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaFormProvider.js
new file mode 100644
index 000000000..46c164a0f
--- /dev/null
+++ b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaFormProvider.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import {
+ useCreateNotifyPaymentReceiveBySMS,
+ usePaymentReceiveSMSDetail,
+} from 'hooks/query';
+
+const NotifyPaymentReceiveViaSMSContext = React.createContext();
+
+function NotifyPaymentReceiveViaFormProvider({
+ paymentReceiveId,
+ dialogName,
+ ...props
+}) {
+ // Create notfiy receipt via sms mutations.
+ const { mutateAsync: createNotifyPaymentReceivetBySMSMutate } =
+ useCreateNotifyPaymentReceiveBySMS();
+
+ const {
+ data: paymentReceiveMSDetail,
+ isLoading: isPaymentReceiveSMSDetailLoading,
+ } = usePaymentReceiveSMSDetail(paymentReceiveId, {
+ enabled: !!paymentReceiveId,
+ });
+
+ // State provider.
+ const provider = {
+ paymentReceiveId,
+ dialogName,
+ paymentReceiveMSDetail,
+ createNotifyPaymentReceivetBySMSMutate,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useNotifyPaymentReceiveViaSMSContext = () =>
+ React.useContext(NotifyPaymentReceiveViaSMSContext);
+
+export {
+ NotifyPaymentReceiveViaFormProvider,
+ useNotifyPaymentReceiveViaSMSContext,
+};
diff --git a/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaSMSContent.js b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaSMSContent.js
new file mode 100644
index 000000000..8b1f49934
--- /dev/null
+++ b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaSMSContent.js
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import { NotifyPaymentReceiveViaFormProvider } from './NotifyPaymentReceiveViaFormProvider';
+import NotifyPaymentReceiveViaSMSForm from './NotifyPaymentReceiveViaSMSForm';
+
+export default function NotifyPaymentReceiveViaSMSContent({
+ // #ownProps
+ dialogName,
+ paymentReceive,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaSMSForm.js b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaSMSForm.js
new file mode 100644
index 000000000..684266739
--- /dev/null
+++ b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/NotifyPaymentReceiveViaSMSForm.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { Intent } from '@blueprintjs/core';
+import { AppToaster } from 'components';
+
+import NotifyViaSMSForm from '../../NotifyViaSMS/NotifyViaSMSForm';
+import { useNotifyPaymentReceiveViaSMSContext } from './NotifyPaymentReceiveViaFormProvider';
+import { transformErrors } from '../../../containers/NotifyViaSMS/utils';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+const notificationType = {
+ key: 'payment-receive-details',
+ label: 'Payment receive thank you.',
+};
+
+/**
+ * Notify Payment Recive Via SMS Form.
+ */
+function NotifyPaymentReceiveViaSMSForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const {
+ dialogName,
+ paymentReceiveId,
+ paymentReceiveMSDetail,
+ createNotifyPaymentReceivetBySMSMutate,
+ } = useNotifyPaymentReceiveViaSMSContext();
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get(
+ 'notify_payment_receive_via_sms.dialog.success_message',
+ ),
+ intent: Intent.SUCCESS,
+ });
+ closeDialog(dialogName);
+ };
+
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ if (errors) {
+ transformErrors(errors, { setErrors });
+ }
+ setSubmitting(false);
+ };
+ createNotifyPaymentReceivetBySMSMutate([paymentReceiveId, values])
+ .then(onSuccess)
+ .catch(onError);
+ };
+ // Handle the form cancel.
+ const handleFormCancel = () => {
+ closeDialog(dialogName);
+ };
+
+ // Form initial values.
+ const initialValues = React.useMemo(
+ () => ({ ...paymentReceiveMSDetail }),
+ [paymentReceiveMSDetail],
+ );
+
+ return (
+
+ );
+}
+export default compose(withDialogActions)(NotifyPaymentReceiveViaSMSForm);
diff --git a/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/index.js b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/index.js
new file mode 100644
index 000000000..f9ce607f9
--- /dev/null
+++ b/src/containers/Dialogs/NotifyPaymentReceiveViaSMSDialog/index.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { FormattedMessage as T } from 'components';
+import { Dialog, DialogSuspense } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose } from 'utils';
+
+const NotifyPaymentReceiveViaSMSDialogContent = React.lazy(() =>
+ import('./NotifyPaymentReceiveViaSMSContent'),
+);
+
+function NotifyPaymentReciveViaSMSDialog({
+ dialogName,
+ payload: { paymentReceiveId },
+ isOpen,
+}) {
+ return (
+ }
+ isOpen={isOpen}
+ canEscapeJeyClose={true}
+ autoFocus={true}
+ className={'dialog--notify-vis-sms'}
+ >
+
+
+
+
+ );
+}
+export default compose(withDialogRedux())(NotifyPaymentReciveViaSMSDialog);
diff --git a/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSDialogContent.js b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSDialogContent.js
new file mode 100644
index 000000000..7f4e40e04
--- /dev/null
+++ b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSDialogContent.js
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import { NotifyReceiptViaSMSFormProvider } from './NotifyReceiptViaSMSFormProvider';
+import NotifyReceiptViaSMSForm from './NotifyReceiptViaSMSForm';
+
+export default function NotifyReceiptViaSMSDialogContent({
+ // #ownProps
+ dialogName,
+ receipt,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSForm.js b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSForm.js
new file mode 100644
index 000000000..2b95a0d35
--- /dev/null
+++ b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSForm.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { Intent } from '@blueprintjs/core';
+import { AppToaster } from 'components';
+
+import NotifyViaSMSForm from '../../NotifyViaSMS/NotifyViaSMSForm';
+import { useNotifyReceiptViaSMSContext } from './NotifyReceiptViaSMSFormProvider';
+import { transformErrors } from '../../../containers/NotifyViaSMS/utils';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+const notificationType = {
+ key: 'sale-receipt-details',
+ label: 'Sale receipt details',
+};
+
+/**
+ * Notify Receipt Via SMS Form.
+ */
+function NotifyReceiptViaSMSForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const {
+ dialogName,
+ receiptId,
+ receiptSMSDetail,
+ createNotifyReceiptBySMSMutate,
+ } = useNotifyReceiptViaSMSContext();
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get('notify_receipt_via_sms.dialog.success_message'),
+ intent: Intent.SUCCESS,
+ });
+ closeDialog(dialogName);
+ };
+
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ if (errors) {
+ transformErrors(errors, { setErrors });
+ }
+ setSubmitting(false);
+ };
+ createNotifyReceiptBySMSMutate([receiptId, values])
+ .then(onSuccess)
+ .catch(onError);
+ };
+ // Handle the form cancel.
+ const handleFormCancel = () => {
+ closeDialog(dialogName);
+ };
+ // Initial values.
+ const initialValues = React.useMemo(
+ () => ({
+ ...receiptSMSDetail,
+ }),
+ [receiptSMSDetail],
+ );
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(NotifyReceiptViaSMSForm);
diff --git a/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSFormProvider.js b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSFormProvider.js
new file mode 100644
index 000000000..cc2a00089
--- /dev/null
+++ b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/NotifyReceiptViaSMSFormProvider.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import { useCreateNotifyReceiptBySMS, useReceiptSMSDetail } from 'hooks/query';
+
+const NotifyReceiptViaSMSContext = React.createContext();
+
+/**
+ *
+ */
+function NotifyReceiptViaSMSFormProvider({ receiptId, dialogName, ...props }) {
+ // Create notfiy receipt via SMS mutations.
+ const { mutateAsync: createNotifyReceiptBySMSMutate } =
+ useCreateNotifyReceiptBySMS();
+
+ // Retrieve the receipt SMS notification details.
+ const { data: receiptSMSDetail, isLoading: isReceiptSMSDetailLoading } =
+ useReceiptSMSDetail(receiptId, {
+ enabled: !!receiptId,
+ });
+
+ // State provider.
+ const provider = {
+ receiptId,
+ dialogName,
+ receiptSMSDetail,
+ createNotifyReceiptBySMSMutate,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useNotifyReceiptViaSMSContext = () =>
+ React.useContext(NotifyReceiptViaSMSContext);
+
+export { NotifyReceiptViaSMSFormProvider, useNotifyReceiptViaSMSContext };
diff --git a/src/containers/Dialogs/NotifyReceiptViaSMSDialog/index.js b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/index.js
new file mode 100644
index 000000000..67ae48813
--- /dev/null
+++ b/src/containers/Dialogs/NotifyReceiptViaSMSDialog/index.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import { FormattedMessage as T } from 'components';
+import { Dialog, DialogSuspense } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose } from 'utils';
+
+const NotifyReceiptViaSMSDialogContent = React.lazy(() =>
+ import('./NotifyReceiptViaSMSDialogContent'),
+);
+
+function NotifyReceiptViaSMSDialog({
+ dialogName,
+ payload: { receiptId },
+ isOpen,
+}) {
+ return (
+ }
+ isOpen={isOpen}
+ canEscapeJeyClose={true}
+ autoFocus={true}
+ className={'dialog--notify-vis-sms'}
+ >
+
+
+
+
+ );
+}
+
+export default compose(withDialogRedux())(NotifyReceiptViaSMSDialog);
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageDialogContent.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageDialogContent.js
new file mode 100644
index 000000000..b7cedf1b3
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageDialogContent.js
@@ -0,0 +1,23 @@
+import React from 'react';
+
+import '../../../style/pages/SMSMessage/SMSMessage.scss';
+import { SMSMessageDialogProvider } from './SMSMessageDialogProvider';
+import SMSMessageForm from './SMSMessageForm';
+
+/**
+ * SMS message dialog content.
+ */
+export default function SMSMessageDialogContent({
+ // #ownProps
+ dialogName,
+ notificationkey,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageDialogProvider.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageDialogProvider.js
new file mode 100644
index 000000000..8aedd7e53
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageDialogProvider.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import {
+ useSettingEditSMSNotification,
+ useSettingSMSNotification,
+} from 'hooks/query';
+
+const SMSMessageDialogContext = React.createContext();
+
+/**
+ * SMS Message dialog provider.
+ */
+function SMSMessageDialogProvider({ notificationkey, dialogName, ...props }) {
+ // Edit SMS message notification mutations.
+ const { mutateAsync: editSMSNotificationMutate } =
+ useSettingEditSMSNotification();
+
+ // SMS notificiation details
+ const { data: smsNotification, isLoading: isSMSNotificationLoading } =
+ useSettingSMSNotification(notificationkey);
+
+ // provider.
+ const provider = {
+ dialogName,
+ smsNotification,
+ editSMSNotificationMutate,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useSMSMessageDialogContext = () =>
+ React.useContext(SMSMessageDialogContext);
+
+export { SMSMessageDialogProvider, useSMSMessageDialogContext };
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.js
new file mode 100644
index 000000000..3f2e8ecc9
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { Formik } from 'formik';
+import { omit } from 'lodash';
+import { Intent } from '@blueprintjs/core';
+
+import { AppToaster } from 'components';
+
+import SMSMessageFormContent from './SMSMessageFormContent';
+import { CreateSMSMessageFormSchema } from './SMSMessageForm.schema';
+import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
+import { transformErrors } from './utils';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+
+import { compose, transformToForm } from 'utils';
+
+const defaultInitialValues = {
+ notification_key: '',
+ is_notification_enabled: '',
+ message_text: '',
+};
+
+/**
+ * SMS Message form.
+ */
+function SMSMessageForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const { dialogName, smsNotification, editSMSNotificationMutate } =
+ useSMSMessageDialogContext();
+
+ // Initial form values.
+ const initialValues = {
+ ...defaultInitialValues,
+ ...transformToForm(smsNotification, defaultInitialValues),
+ notification_key: smsNotification.key,
+ message_text: smsNotification.sms_message,
+ };
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ const form = {
+ ...omit(values, ['is_notification_enabled', 'sms_message']),
+ notification_key: smsNotification.key,
+ };
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get('sms_message.dialog.success_message'),
+ intent: Intent.SUCCESS,
+ });
+ closeDialog(dialogName);
+ };
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ if (errors) {
+ transformErrors(errors, { setErrors });
+ }
+ setSubmitting(false);
+ };
+ editSMSNotificationMutate(form).then(onSuccess).catch(onError);
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(SMSMessageForm);
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.schema.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.schema.js
new file mode 100644
index 000000000..441e73381
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.schema.js
@@ -0,0 +1,11 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+
+const Schema = Yup.object().shape({
+ notification_key: Yup.string().required(),
+ is_notification_enabled: Yup.boolean(),
+ message_text: Yup.string().min(3).max(DATATYPES_LENGTH.TEXT),
+});
+
+export const CreateSMSMessageFormSchema = Schema;
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormContent.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormContent.js
new file mode 100644
index 000000000..c9b56c6ea
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormContent.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { Form, useFormikContext } from 'formik';
+import styled from 'styled-components';
+import { Classes } from '@blueprintjs/core';
+import { castArray } from 'lodash';
+
+import SMSMessageFormFields from './SMSMessageFormFields';
+import SMSMessageFormFloatingActions from './SMSMessageFormFloatingActions';
+
+import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
+import { SMSMessagePreview } from 'components';
+import { getSMSUnits } from '../../NotifyViaSMS/utils';
+
+/**
+ * SMS message form content.
+ */
+export default function SMSMessageFormContent() {
+ // SMS message dialog context.
+ const { smsNotification } = useSMSMessageDialogContext();
+
+ // Ensure always returns array.
+ const messageVariables = React.useMemo(
+ () => castArray(smsNotification.allowed_variables),
+ [smsNotification.allowed_variables],
+ );
+
+ return (
+
+ );
+}
+
+/**
+ * SMS Message preview section.
+ * @returns {JSX}
+ */
+function SMSMessagePreviewSection() {
+ const {
+ values: { message_text: message },
+ } = useFormikContext();
+
+ const messagesUnits = getSMSUnits(message);
+
+ return (
+
+
+
+ {intl.formatHTMLMessage(
+ { id: 'sms_message.dialog.sms_note' },
+ {
+ value: messagesUnits,
+ },
+ )}
+
+
+ );
+}
+
+const SMSPreviewSectionRoot = styled.div``;
+
+const SMSPreviewSectionNote = styled.div`
+ font-size: 12px;
+ opacity: 0.7;
+`;
+
+const SMSMessageVariables = styled.div`
+ list-style: none;
+ font-size: 12px;
+ opacity: 0.9;
+`;
+
+const MessageVariable = styled.div`
+ margin-bottom: 8px;
+`;
+
+const FormContent = styled.div`
+ display: flex;
+`;
+const FormFields = styled.div`
+ width: 55%;
+`;
+const FormPreview = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 45%;
+ padding-left: 25px;
+ margin-left: 25px;
+ border-left: 1px solid #dcdcdd;
+`;
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFields.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFields.js
new file mode 100644
index 000000000..543a842da
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFields.js
@@ -0,0 +1,65 @@
+import React from 'react';
+import styled from 'styled-components';
+import { useFormikContext, FastField, ErrorMessage } from 'formik';
+import { Intent, Button, FormGroup, TextArea } from '@blueprintjs/core';
+
+import { FormattedMessage as T } from 'components';
+
+import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
+
+import { inputIntent } from 'utils';
+
+/**
+ *
+ */
+export default function SMSMessageFormFields() {
+ // SMS message dialog context.
+ const { smsNotification } = useSMSMessageDialogContext();
+
+ // Form formik context.
+ const { setFieldValue } = useFormikContext();
+
+ // Handle the button click.
+ const handleBtnClick = () => {
+ setFieldValue('message_text', smsNotification.default_sms_message);
+ };
+
+ return (
+
+ {/* ----------- Message Text ----------- */}
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ className={'form-group--message_text'}
+ intent={inputIntent({ error, touched })}
+ helperText={
+ <>
+
+
+ Reset to default message.
+
+ >
+ }
+ >
+
+
+ )}
+
+
+ );
+}
+
+const ResetButton = styled(Button)`
+ font-size: 12px;
+`;
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFloatingActions.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFloatingActions.js
new file mode 100644
index 000000000..e823fba9d
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFloatingActions.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import { Intent, Button, Classes } from '@blueprintjs/core';
+import { useFormikContext } from 'formik';
+
+import { DialogFooterActions, FormattedMessage as T } from 'components';
+
+import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+
+import { compose } from 'utils';
+
+/**
+ * SMS Message Form floating actions.
+ */
+function SMSMessageFormFloatingActions({
+ // #withDialogActions
+ closeDialog,
+}) {
+ // Formik context.
+ const { isSubmitting } = useFormikContext();
+
+ // SMS Message dialog contxt.
+ const { dialogName } = useSMSMessageDialogContext();
+
+ // Handle close button click.
+ const handleCancelBtnClick = () => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default compose(withDialogActions)(SMSMessageFormFloatingActions);
diff --git a/src/containers/Dialogs/SMSMessageDialog/index.js b/src/containers/Dialogs/SMSMessageDialog/index.js
new file mode 100644
index 000000000..aadb1b13e
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/index.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { Dialog, DialogSuspense } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+
+import { compose } from 'redux';
+
+const SMSMessageDialogContent = React.lazy(() =>
+ import('./SMSMessageDialogContent'),
+);
+
+/**
+ * SMS Message dialog.
+ */
+function SMSMessageDialog({
+ dialogName,
+ payload: { notificationkey },
+ isOpen,
+}) {
+ return (
+
+ );
+}
+
+export default compose(withDialogRedux())(SMSMessageDialog);
diff --git a/src/containers/Dialogs/SMSMessageDialog/utils.js b/src/containers/Dialogs/SMSMessageDialog/utils.js
new file mode 100644
index 000000000..6b94176bd
--- /dev/null
+++ b/src/containers/Dialogs/SMSMessageDialog/utils.js
@@ -0,0 +1,19 @@
+import { Intent } from '@blueprintjs/core';
+import { castArray } from 'lodash';
+
+export const transformErrors = (errors, { setErrors }) => {
+ let unsupportedVariablesError = errors.find(
+ (error) => error.type === 'UNSUPPORTED_SMS_MESSAGE_VARIABLES',
+ );
+ if (unsupportedVariablesError) {
+ const variables = castArray(
+ unsupportedVariablesError.data.unsupported_args,
+ );
+ const stringifiedVariables = variables.join(', ');
+
+ setErrors({
+ message_text: `The SMS message has unsupported variables - ${stringifiedVariables}`,
+ intent: Intent.DANGER,
+ });
+ }
+};
diff --git a/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js b/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js
index 0c468be41..1a7c3c051 100644
--- a/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js
+++ b/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js
@@ -15,7 +15,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
-import { Icon, FormattedMessage as T } from 'components';
+import { Icon, FormattedMessage as T, MoreMenuItems } from 'components';
import { compose } from 'utils';
@@ -51,6 +51,10 @@ function EstimateDetailActionsBar({
const handlePrintEstimate = () => {
openDialog('estimate-pdf-preview', { estimateId });
};
+ // Handle notify via SMS.
+ const handleNotifyViaSMS = () => {
+ openDialog('notify-estimate-via-sms', { estimateId });
+ };
return (
@@ -75,6 +79,12 @@ function EstimateDetailActionsBar({
intent={Intent.DANGER}
onClick={handleDeleteEstimate}
/>
+
+
);
diff --git a/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js b/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js
index b83b8a6ed..b4fa903b7 100644
--- a/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js
+++ b/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js
@@ -6,28 +6,17 @@ import {
NavbarGroup,
Classes,
NavbarDivider,
- Popover,
- PopoverInteractionKind,
- Position,
Intent,
- MenuItem,
- Menu,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
-import { moreVertOptions } from '../../../common/moreVertOptions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
-import {
- If,
- Icon,
- FormattedMessage as T,
- // MoreVertMenutItems,
-} from 'components';
+import { If, Icon, FormattedMessage as T } from 'components';
import { compose } from 'utils';
@@ -76,6 +65,10 @@ function InvoiceDetailActionsBar({
const handleBadDebtInvoice = () => {
openDialog('write-off-bad-debt', { invoiceId });
};
+ // Handle notify via SMS.
+ const handleNotifyViaSMS = () => {
+ openDialog('notify-invoice-via-sms', { invoiceId });
+ };
// Handle cancele write-off invoice.
const handleCancelBadDebtInvoice = () => {
@@ -116,9 +109,11 @@ function InvoiceDetailActionsBar({
/>
diff --git a/src/containers/Drawers/InvoiceDetailDrawer/utils.js b/src/containers/Drawers/InvoiceDetailDrawer/utils.js
index 26bd03fab..68c4cbc29 100644
--- a/src/containers/Drawers/InvoiceDetailDrawer/utils.js
+++ b/src/containers/Drawers/InvoiceDetailDrawer/utils.js
@@ -10,6 +10,7 @@ import {
} from '@blueprintjs/core';
import { Icon, FormattedMessage as T, Choose } from 'components';
import { FormatNumberCell } from '../../../components';
+import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
/**
* Retrieve invoice readonly details table columns.
@@ -58,7 +59,11 @@ export const useInvoiceReadonlyEntriesColumns = () =>
[],
);
-export const BadDebtMenuItem = ({ invoice, onDialog, onAlert }) => {
+export const BadDebtMenuItem = ({
+ payload: { onCancelBadDebt, onBadDebt, onNotifyViaSMS },
+}) => {
+ const { invoice } = useInvoiceDetailDrawerContext();
+
return (
{
}
- onClick={onDialog}
+ onClick={onBadDebt}
/>
}
/>
+ }
+ />
}
>
diff --git a/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js b/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js
index 1f4939feb..94bb19c65 100644
--- a/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js
+++ b/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js
@@ -16,7 +16,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
-import { Icon, FormattedMessage as T } from 'components';
+import { Icon, FormattedMessage as T, MoreMenuItems } from 'components';
import { compose } from 'utils';
@@ -29,6 +29,9 @@ function PaymentReceiveActionsBar({
// #withDrawerActions
closeDrawer,
+
+ // #withDialogActions
+ openDialog,
}) {
const history = useHistory();
@@ -46,6 +49,11 @@ function PaymentReceiveActionsBar({
openAlert('payment-receive-delete', { paymentReceiveId });
};
+ // Handle notify via SMS.
+ const handleNotifyViaSMS = () => {
+ openDialog('notify-payment-via-sms', { paymentReceiveId });
+ };
+
return (
@@ -63,6 +71,12 @@ function PaymentReceiveActionsBar({
intent={Intent.DANGER}
onClick={handleDeletePaymentReceive}
/>
+
+
);
diff --git a/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js b/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js
index b6e17c191..608805a00 100644
--- a/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js
+++ b/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js
@@ -14,7 +14,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
-import { Icon, FormattedMessage as T } from 'components';
+import { Icon, FormattedMessage as T, MoreMenuItems } from 'components';
import { useReceiptDetailDrawerContext } from './ReceiptDetailDrawerProvider';
import { safeCallback, compose } from 'utils';
@@ -46,6 +46,11 @@ function ReceiptDetailActionBar({
const onPrintReceipt = () => {
openDialog('receipt-pdf-preview', { receiptId });
};
+
+ // Handle notify via SMS.
+ const handleNotifyViaSMS = () => {
+ openDialog('notify-receipt-via-sms', { receiptId });
+ };
return (
@@ -69,6 +74,12 @@ function ReceiptDetailActionBar({
intent={Intent.DANGER}
onClick={safeCallback(onDeleteReceipt)}
/>
+
+
);
diff --git a/src/containers/NotifyViaSMS/NotifyViaSMSForm.js b/src/containers/NotifyViaSMS/NotifyViaSMSForm.js
new file mode 100644
index 000000000..2f9721669
--- /dev/null
+++ b/src/containers/NotifyViaSMS/NotifyViaSMSForm.js
@@ -0,0 +1,156 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { castArray, includes } from 'lodash';
+import { Formik, Form, useFormikContext } from 'formik';
+import styled from 'styled-components';
+import { Callout, Classes, Intent } from '@blueprintjs/core';
+
+import 'style/pages/NotifyConactViaSMS/NotifyConactViaSMSDialog.scss';
+
+import { CreateNotifyViaSMSFormSchema } from './NotifyViaSMSForm.schema';
+import NotifyViaSMSFormFields from './NotifyViaSMSFormFields';
+import NotifyViaSMSFormFloatingActions from './NotifyViaSMSFormFloatingActions';
+import { FormObserver, SMSMessagePreview } from 'components';
+
+import { transformToForm, safeInvoke } from 'utils';
+import { getSMSUnits } from './utils';
+
+const defaultInitialValues = {
+ notification_key: '',
+ customer_name: '',
+ customer_phone_number: '',
+ sms_message: '',
+};
+
+/**
+ * Notify via sms - SMS message preview section.
+ */
+function SMSMessagePreviewSection() {
+ const {
+ values: { sms_message },
+ } = useFormikContext();
+
+ // Calculates the SMS units of message.
+ const messagesUnits = getSMSUnits(sms_message);
+
+ return (
+
+
+
+ {intl.formatHTMLMessage(
+ { id: 'notiify_via_sms.dialog.sms_note' },
+ {
+ value: messagesUnits,
+ },
+ )}
+
+
+ );
+}
+
+/**
+ * Notify Via SMS Form.
+ */
+function NotifyViaSMSForm({
+ initialValues: initialValuesComponent,
+ notificationTypes,
+ onSubmit,
+ onCancel,
+ onValuesChange,
+ calloutCodes,
+ formikProps,
+}) {
+ // Initial form values
+ const initialValues = {
+ ...defaultInitialValues,
+ ...transformToForm(initialValuesComponent, defaultInitialValues),
+ };
+ // Ensure always returns array.
+ const formattedNotificationTypes = React.useMemo(
+ () => castArray(notificationTypes),
+ [notificationTypes],
+ );
+
+ return (
+
+
+
+ );
+}
+
+/**
+ * Observes the values change of notify form.
+ */
+function NotifyObserveValuesChange({ onChange }) {
+ const { values } = useFormikContext();
+
+ // Handle the form change observe.
+ const handleChange = () => {
+ safeInvoke(onChange, values);
+ };
+ return ;
+}
+
+/**
+ * Notify via SMS form alerts.
+ */
+function NotifyViaSMSAlerts({ calloutCodes }) {
+ return [
+ includes(calloutCodes, 100) && (
+
+ {intl.get('notify_Via_sms.dialog.customer_phone_number_does_not_eixst')}
+
+ ),
+ includes(calloutCodes, 200) && (
+
+ {intl.get('notify_Via_sms.dialog.customer_phone_number_invalid')}
+
+ ),
+ ];
+}
+
+export default NotifyViaSMSForm;
+
+const NotifyContent = styled.div`
+ display: flex;
+`;
+
+const NotifyFieldsSection = styled.div`
+ flex: 1;
+ width: 65%;
+`;
+
+const SMSPreviewSectionRoot = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 45%;
+ padding-left: 25px;
+ margin-left: 25px;
+ border-left: 1px solid #dcdcdd;
+`;
+
+const SMSPreviewSectionNote = styled.div`
+ font-size: 12px;
+ opacity: 0.7;
+`;
diff --git a/src/containers/NotifyViaSMS/NotifyViaSMSForm.schema.js b/src/containers/NotifyViaSMS/NotifyViaSMSForm.schema.js
new file mode 100644
index 000000000..ff3114609
--- /dev/null
+++ b/src/containers/NotifyViaSMS/NotifyViaSMSForm.schema.js
@@ -0,0 +1,11 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+
+const Schema = Yup.object().shape({
+ customer_name: Yup.string().required(),
+ customer_phone_number: Yup.number(),
+ sms_message: Yup.string().required().trim().max(DATATYPES_LENGTH.TEXT),
+});
+
+export const CreateNotifyViaSMSFormSchema = Schema;
diff --git a/src/containers/NotifyViaSMS/NotifyViaSMSFormFields.js b/src/containers/NotifyViaSMS/NotifyViaSMSFormFields.js
new file mode 100644
index 000000000..3b08b070e
--- /dev/null
+++ b/src/containers/NotifyViaSMS/NotifyViaSMSFormFields.js
@@ -0,0 +1,86 @@
+import React from 'react';
+import { FastField, ErrorMessage } from 'formik';
+import { FormGroup, InputGroup } from '@blueprintjs/core';
+import classNames from 'classnames';
+import styled from 'styled-components';
+
+import {
+ ListSelect,
+ FieldRequiredHint,
+ FormattedMessage as T,
+} from 'components';
+import { CLASSES } from 'common/classes';
+import { inputIntent } from 'utils';
+
+export default function NotifyViaSMSFormFields({ notificationTypes }) {
+ return (
+
+
+ {({ form, meta: { error, touched } }) => (
+ }
+ className={classNames(CLASSES.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+ {
+ form.setFieldValue('notification_key', notification.key);
+ }}
+ disabled={notificationTypes.length < 2}
+ />
+
+ )}
+
+
+ {/* ----------- Send Notification to ----------- */}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ className={classNames('form-group--customer-name', CLASSES.FILL)}
+ labelInfo={}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ )}
+
+
+ {/* ----------- Phone number ----------- */}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ className={classNames(
+ 'form-group--customer_phone_number',
+ CLASSES.FILL,
+ )}
+ >
+
+
+ )}
+
+
+ );
+}
+
+const NotifyViaSMSFormFieldsRoot = styled.div``;
diff --git a/src/containers/NotifyViaSMS/NotifyViaSMSFormFloatingActions.js b/src/containers/NotifyViaSMS/NotifyViaSMSFormFloatingActions.js
new file mode 100644
index 000000000..dbe701aaf
--- /dev/null
+++ b/src/containers/NotifyViaSMS/NotifyViaSMSFormFloatingActions.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import { useFormikContext } from 'formik';
+import { Intent, Button, Classes } from '@blueprintjs/core';
+
+import { DialogFooterActions, FormattedMessage as T } from 'components';
+
+/**
+ *
+ */
+export default function NotifyViaSMSFormFloatingActions({ onCancel }) {
+ // Formik context.
+ const { isSubmitting } = useFormikContext();
+
+ // Handle close button click.
+ const handleCancelBtnClick = (event) => {
+ onCancel && onCancel(event);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/containers/NotifyViaSMS/utils.js b/src/containers/NotifyViaSMS/utils.js
new file mode 100644
index 000000000..2d6b78593
--- /dev/null
+++ b/src/containers/NotifyViaSMS/utils.js
@@ -0,0 +1,22 @@
+import intl from 'react-intl-universal';
+
+export const transformErrors = (errors, { setErrors, setCalloutCode }) => {
+ if (errors.some((e) => e.type === 'CUSTOMER_SMS_NOTIFY_PHONE_INVALID')) {
+ setCalloutCode([200]);
+ setErrors({
+ customer_phone_number: 'The personal phone number is invalid.',
+ });
+ }
+ if (errors.find((error) => error.type === 'CUSTOMER_HAS_NO_PHONE_NUMBER')) {
+ setCalloutCode([100]);
+ setErrors({
+ customer_phone_number: intl.get(
+ 'notify_via_sms.dialog.customer_no_phone_error_message',
+ ),
+ });
+ }
+};
+
+export const getSMSUnits = (message, threshold = 140) => {
+ return Math.ceil(message.length / threshold);
+};
diff --git a/src/containers/Preferences/SMSIntegration/SMSIntegrationProvider.js b/src/containers/Preferences/SMSIntegration/SMSIntegrationProvider.js
new file mode 100644
index 000000000..23b03f148
--- /dev/null
+++ b/src/containers/Preferences/SMSIntegration/SMSIntegrationProvider.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import { useSettings, useSettingSMSNotifications } from 'hooks/query';
+import PreferencesPageLoader from '../PreferencesPageLoader';
+
+const SMSIntegrationContext = React.createContext();
+
+/**
+ * SMS Integration provider.
+ */
+function SMSIntegrationProvider({ ...props }) {
+ //Fetches Organization Settings.
+ const { isLoading: isSettingsLoading } = useSettings();
+
+ const { data: notifications, isLoading: isSMSNotificationsLoading } =
+ useSettingSMSNotifications();
+
+ // Provider state.
+ const provider = {
+ notifications,
+ isSMSNotificationsLoading,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useSMSIntegrationContext = () => React.useContext(SMSIntegrationContext);
+
+export { SMSIntegrationProvider, useSMSIntegrationContext };
diff --git a/src/containers/Preferences/SMSIntegration/SMSIntegrationTabs.js b/src/containers/Preferences/SMSIntegration/SMSIntegrationTabs.js
new file mode 100644
index 000000000..ebad8a644
--- /dev/null
+++ b/src/containers/Preferences/SMSIntegration/SMSIntegrationTabs.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { Tabs, Tab } from '@blueprintjs/core';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import SMSMessagesDataTable from './SMSMessagesDataTable';
+
+import '../../../style/pages/Preferences/SMSIntegration.scss';
+
+import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+
+import { compose } from 'utils';
+
+function SMSIntegrationTabs({
+ // #withDashboardActions
+ changePreferencesPageTitle,
+}) {
+ React.useEffect(() => {
+ changePreferencesPageTitle(intl.get('sms_integration.label'));
+ }, [changePreferencesPageTitle]);
+
+ return (
+
+ );
+}
+
+export default compose(withDashboardActions)(SMSIntegrationTabs);
diff --git a/src/containers/Preferences/SMSIntegration/SMSMessagesDataTable.js b/src/containers/Preferences/SMSIntegration/SMSMessagesDataTable.js
new file mode 100644
index 000000000..b2bb18603
--- /dev/null
+++ b/src/containers/Preferences/SMSIntegration/SMSMessagesDataTable.js
@@ -0,0 +1,86 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import styled from 'styled-components';
+import { Intent } from '@blueprintjs/core';
+
+import { DataTable, AppToaster } from 'components';
+import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
+
+import { useSMSIntegrationTableColumns, ActionsMenu } from './components';
+import { useSMSIntegrationContext } from './SMSIntegrationProvider';
+import { useSettingEditSMSNotification } from 'hooks/query';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+/**
+ * SMS Message data table.
+ */
+function SMSMessagesDataTable({
+ // #withDialogAction
+ openDialog,
+}) {
+ // Edit SMS message notification mutations.
+ const { mutateAsync: editSMSNotificationMutate } =
+ useSettingEditSMSNotification();
+
+ // Handle notification switch change.
+ const handleNotificationSwitchChange = React.useCallback(
+ (event, value, notification) => {
+ editSMSNotificationMutate({
+ notification_key: notification.key,
+ is_notification_enabled: value,
+ }).then(() => {
+ AppToaster.show({
+ message: intl.get(
+ 'sms_messages.notification_switch_change_success_message',
+ ),
+ intent: Intent.SUCCESS,
+ });
+ });
+ },
+ [editSMSNotificationMutate],
+ );
+
+ // Table columns.
+ const columns = useSMSIntegrationTableColumns({
+ onSwitchChange: handleNotificationSwitchChange,
+ });
+
+ const { notifications, isSMSNotificationsLoading } =
+ useSMSIntegrationContext();
+
+ // handle edit message link click
+ const handleEditMessageText = ({ key }) => {
+ openDialog('sms-message-form', { notificationkey: key });
+ };
+
+ const handleEnableNotification = () => {};
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(SMSMessagesDataTable);
+
+const SMSNotificationsTable = styled(DataTable)`
+ .table .tbody .tr .td {
+ align-items: flex-start;
+ }
+ .table .tbody .td {
+ padding: 0.8rem;
+ }
+`;
diff --git a/src/containers/Preferences/SMSIntegration/components.js b/src/containers/Preferences/SMSIntegration/components.js
new file mode 100644
index 000000000..8c591a15f
--- /dev/null
+++ b/src/containers/Preferences/SMSIntegration/components.js
@@ -0,0 +1,137 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import styled from 'styled-components';
+import { Intent, Button, Menu, MenuItem } from '@blueprintjs/core';
+
+import { SwitchFieldCell } from 'components/DataTableCells';
+
+import { safeInvoke } from 'utils';
+
+/**
+ * Notification accessor.
+ */
+export const NotificationAccessor = (row) => {
+ return (
+
+ {row.notification_label}
+
+ {row.notification_description}
+
+
+ );
+};
+
+/**
+ * SMS notification message cell.
+ */
+export const SMSMessageCell = ({
+ payload: { onEditMessageText },
+ row: { original },
+}) => (
+
+ {original.sms_message}
+
+
+
+
+);
+
+/**
+ * Context menu of SMS notification messages.
+ */
+export function ActionsMenu({
+ payload: { onEditMessageText, onEnableNotification },
+ row: { original },
+}) {
+ return (
+
+ );
+}
+
+/**
+ * Retrieve SMS notifications messages table columns
+ * @returns
+ */
+export function useSMSIntegrationTableColumns({ onSwitchChange }) {
+ return React.useMemo(
+ () => [
+ {
+ id: 'notification',
+ Header: intl.get('sms_messages.label_notification'),
+ accessor: NotificationAccessor,
+ className: 'notification',
+ width: '180',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('service'),
+ accessor: 'module_formatted',
+ className: 'service',
+ width: '80',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('sms_messages.label_mesage'),
+ accessor: 'sms_message',
+ Cell: SMSMessageCell,
+ className: 'sms_message',
+ width: '180',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('sms_messages.label_auto'),
+ accessor: 'is_notification_enabled',
+ Cell: SwitchFieldCell,
+ className: 'is_notification_enabled',
+ disableResizing: true,
+ disableSortBy: true,
+ width: '80',
+ onSwitchChange,
+ },
+ ],
+ [onSwitchChange],
+ );
+}
+
+const NotificationLabel = styled.div`
+ font-weight: 500;
+`;
+
+const NotificationDescription = styled.div`
+ font-size: 14px;
+ margin-top: 6px;
+ display: block;
+ opacity: 0.75;
+`;
+
+const MessageBox = styled.div`
+ padding: 10px;
+ background-color: #fbfbfb;
+ border: 1px dashed #dcdcdc;
+ font-size: 14px;
+ line-height: 1.45;
+`;
+
+const MessageBoxActions = styled.div`
+ margin-top: 2px;
+
+ button {
+ font-size: 12px;
+ }
+`;
diff --git a/src/containers/Preferences/SMSIntegration/index.js b/src/containers/Preferences/SMSIntegration/index.js
new file mode 100644
index 000000000..bd3d7d5cc
--- /dev/null
+++ b/src/containers/Preferences/SMSIntegration/index.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { SMSIntegrationProvider } from './SMSIntegrationProvider';
+import SMSIntegrationTabs from './SMSIntegrationTabs';
+
+/**
+ * SMS SMS Integration
+ */
+export default function SMSIntegration() {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Preferences/Users/components.js b/src/containers/Preferences/Users/components.js
index 6b288a0df..853852a61 100644
--- a/src/containers/Preferences/Users/components.js
+++ b/src/containers/Preferences/Users/components.js
@@ -78,7 +78,7 @@ export function ActionsMenu({
*/
function StatusAccessor(user) {
return !user.is_invite_accepted ? (
-
+
) : user.active ? (
diff --git a/src/hooks/query/customers.js b/src/hooks/query/customers.js
index 3e397972a..75a6d6d3b 100644
--- a/src/hooks/query/customers.js
+++ b/src/hooks/query/customers.js
@@ -20,6 +20,12 @@ const commonInvalidateQueries = (queryClient) => {
// Invalidate the financial reports.
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
+
+ // Invalidate SMS details.
+ queryClient.invalidateQueries(t.SALE_ESTIMATE_SMS_DETAIL);
+ queryClient.invalidateQueries(t.SALE_INVOICE_SMS_DETAIL);
+ queryClient.invalidateQueries(t.SALE_RECEIPT_SMS_DETAIL);
+ queryClient.invalidateQueries(t.PAYMENT_RECEIVE_SMS_DETAIL);
};
// Customers response selector.
diff --git a/src/hooks/query/estimates.js b/src/hooks/query/estimates.js
index 8c25c4baa..b73eae6ce 100644
--- a/src/hooks/query/estimates.js
+++ b/src/hooks/query/estimates.js
@@ -189,3 +189,49 @@ export function useRefreshEstimates() {
},
};
}
+
+/**
+ *
+ */
+export function useCreateNotifyEstimateBySMS(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ ([id, values]) =>
+ apiRequest.post(`sales/estimates/${id}/notify-by-sms`, values),
+ {
+ onSuccess: (res, [id, values]) => {
+ // Invalidate
+ queryClient.invalidateQueries([t.NOTIFY_SALE_ESTIMATE_BY_SMS, id]);
+
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ *
+ * @param {*} estimateId
+ * @param {*} props
+ * @param {*} requestProps
+ * @returns
+ */
+export function useEstimateSMSDetail(estimateId, props, requestProps) {
+ return useRequestQuery(
+ [t.SALE_ESTIMATE_SMS_DETAIL, estimateId],
+ {
+ method: 'get',
+ url: `sales/estimates/${estimateId}/sms-details`,
+ ...requestProps,
+ },
+ {
+ select: (res) => res.data.data,
+ defaultData: {},
+ ...props,
+ },
+ );
+}
diff --git a/src/hooks/query/invoices.js b/src/hooks/query/invoices.js
index 67debc80c..aabe2fa07 100644
--- a/src/hooks/query/invoices.js
+++ b/src/hooks/query/invoices.js
@@ -219,14 +219,53 @@ export function useCancelBadDebt(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
- return useMutation((id) => apiRequest.post(`sales/invoices/${id}/writeoff/cancel`), {
- onSuccess: (res, id) => {
- // Invalidate
- queryClient.invalidateQueries([t.CANCEL_BAD_DEBT, id]);
+ return useMutation(
+ (id) => apiRequest.post(`sales/invoices/${id}/writeoff/cancel`),
+ {
+ onSuccess: (res, id) => {
+ // Invalidate
+ queryClient.invalidateQueries([t.CANCEL_BAD_DEBT, id]);
- // Common invalidate queries.
- commonInvalidateQueries(queryClient);
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
},
- ...props,
- });
+ );
+}
+
+export function useCreateNotifyInvoiceBySMS(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ ([id, values]) =>
+ apiRequest.post(`sales/invoices/${id}/notify-by-sms`, values),
+ {
+ onSuccess: (res, [id, values]) => {
+ // Invalidate
+ queryClient.invalidateQueries([t.NOTIFY_SALE_INVOICE_BY_SMS, id]);
+
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ },
+ );
+}
+
+export function useInvoiceSMSDetail(invoiceId, query, props) {
+ return useRequestQuery(
+ [t.SALE_INVOICE_SMS_DETAIL, invoiceId, query],
+ {
+ method: 'get',
+ url: `sales/invoices/${invoiceId}/sms-details`,
+ params: query,
+ },
+ {
+ select: (res) => res.data.data,
+ defaultData: {},
+ ...props,
+ },
+ );
}
diff --git a/src/hooks/query/paymentReceives.js b/src/hooks/query/paymentReceives.js
index 6f8a0f28a..06e7b39cb 100644
--- a/src/hooks/query/paymentReceives.js
+++ b/src/hooks/query/paymentReceives.js
@@ -174,3 +174,43 @@ export function useRefreshPaymentReceive() {
},
};
}
+
+export function useCreateNotifyPaymentReceiveBySMS(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ ([id, values]) =>
+ apiRequest.post(`sales/payment_receives/${id}/notify-by-sms`, values),
+ {
+ onSuccess: (res, [id, values]) => {
+ // Invalidate
+ queryClient.invalidateQueries([t.NOTIFY_PAYMENT_RECEIVE_BY_SMS, id]);
+
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ },
+ );
+}
+
+export function usePaymentReceiveSMSDetail(
+ paymentReceiveId,
+ props,
+ requestProps,
+) {
+ return useRequestQuery(
+ [t.PAYMENT_RECEIVE_SMS_DETAIL, paymentReceiveId],
+ {
+ method: 'get',
+ url: `sales/payment_receives/${paymentReceiveId}/sms-details`,
+ ...requestProps,
+ },
+ {
+ select: (res) => res.data.data,
+ defaultData: {},
+ ...props,
+ },
+ );
+}
diff --git a/src/hooks/query/receipts.js b/src/hooks/query/receipts.js
index ebefb6c26..a9b0c705b 100644
--- a/src/hooks/query/receipts.js
+++ b/src/hooks/query/receipts.js
@@ -23,7 +23,7 @@ const commonInvalidateQueries = (queryClient) => {
// Invalidate the cashflow transactions.
queryClient.invalidateQueries(t.CASH_FLOW_TRANSACTIONS);
queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY);
-
+
// Invalidate the settings.
queryClient.invalidateQueries([t.SETTING, t.SETTING_RECEIPTS]);
};
@@ -163,3 +163,37 @@ export function useRefreshReceipts() {
},
};
}
+
+export function useCreateNotifyReceiptBySMS(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+ return useMutation(
+ ([id, values]) =>
+ apiRequest.post(`sales/receipts/${id}/notify-by-sms`, values),
+ {
+ onSuccess: (res, [id, values]) => {
+ queryClient.invalidateQueries([t.NOTIFY_SALE_RECEIPT_BY_SMS, id]);
+
+ // Invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ },
+ );
+}
+
+export function useReceiptSMSDetail(receiptId, props, requestProps) {
+ return useRequestQuery(
+ [t.SALE_RECEIPT_SMS_DETAIL, receiptId],
+ {
+ method: 'get',
+ url: `sales/receipts/${receiptId}/sms-details`,
+ ...requestProps,
+ },
+ {
+ select: (res) => res.data.data,
+ defaultData: {},
+ ...props,
+ },
+ );
+}
diff --git a/src/hooks/query/settings.js b/src/hooks/query/settings.js
index 22e25c72b..b7c70b335 100644
--- a/src/hooks/query/settings.js
+++ b/src/hooks/query/settings.js
@@ -1,4 +1,3 @@
-import { useEffect } from 'react';
import { useMutation, useQueryClient } from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import useApiRequest from '../useRequest';
@@ -123,3 +122,61 @@ export function useSettingCashFlow(props) {
props,
);
}
+
+/**
+ * Retrieve SMS Notifications settings.
+ */
+export function useSettingSMSNotifications(props) {
+ return useRequestQuery(
+ [t.SETTING_SMS_NOTIFICATIONS],
+ { method: 'get', url: `settings/sms-notifications` },
+ {
+ select: (res) => res.data.notifications,
+ defaultData: [],
+ ...props,
+ },
+ );
+}
+
+/**
+ * Retrieve Specific SMS Notification settings.
+ */
+export function useSettingSMSNotification(key, props) {
+ return useRequestQuery(
+ [t.SETTING_SMS_NOTIFICATIONS, key],
+ {
+ method: 'get',
+ url: `settings/sms-notification/${key}`,
+ },
+ {
+ select: (res) => res.data.notification,
+ defaultData: {
+ smsNotification: [],
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Retrieve Edit SMS Notification settings.
+ */
+export function useSettingEditSMSNotification(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ (values) => apiRequest.post(`settings/sms-notification`, values),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries([t.SETTING_SMS_NOTIFICATIONS]);
+
+ queryClient.invalidateQueries(t.SALE_INVOICE_SMS_DETAIL);
+ queryClient.invalidateQueries(t.SALE_RECEIPT_SMS_DETAIL);
+ queryClient.invalidateQueries(t.PAYMENT_RECEIVE_SMS_DETAIL);
+ queryClient.invalidateQueries(t.SALE_ESTIMATE_SMS_DETAIL);
+ },
+ ...props,
+ },
+ );
+}
diff --git a/src/hooks/query/types.js b/src/hooks/query/types.js
index 54952a47c..1aebbd8e1 100644
--- a/src/hooks/query/types.js
+++ b/src/hooks/query/types.js
@@ -51,11 +51,15 @@ const ITEMS = {
const SALE_ESTIMATES = {
SALE_ESTIMATES: 'SALE_ESTIMATES',
SALE_ESTIMATE: 'SALE_ESTIMATE',
+ SALE_ESTIMATE_SMS_DETAIL: 'SALE_ESTIMATE_SMS_DETAIL',
+ NOTIFY_SALE_ESTIMATE_BY_SMS: 'NOTIFY_SALE_ESTIMATE_BY_SMS',
};
const SALE_RECEIPTS = {
SALE_RECEIPTS: 'SALE_RECEIPTS',
SALE_RECEIPT: 'SALE_RECEIPT',
+ SALE_RECEIPT_SMS_DETAIL: 'SALE_RECEIPT_SMS_DETAIL',
+ NOTIFY_SALE_RECEIPT_BY_SMS: 'NOTIFY_SALE_RECEIPT_BY_SMS',
};
const INVENTORY_ADJUSTMENTS = {
@@ -79,12 +83,16 @@ const PAYMENT_RECEIVES = {
PAYMENT_RECEIVE: 'PAYMENT_RECEIVE',
PAYMENT_RECEIVE_NEW_ENTRIES: 'PAYMENT_RECEIVE_NEW_ENTRIES',
PAYMENT_RECEIVE_EDIT_PAGE: 'PAYMENT_RECEIVE_EDIT_PAGE',
+ PAYMENT_RECEIVE_SMS_DETAIL: 'PAYMENT_RECEIVE_SMS_DETAIL',
+ NOTIFY_PAYMENT_RECEIVE_BY_SMS: 'NOTIFY_PAYMENT_RECEIVE_BY_SMS',
};
const SALE_INVOICES = {
SALE_INVOICES: 'SALE_INVOICES',
SALE_INVOICE: 'SALE_INVOICE',
SALE_INVOICES_DUE: 'SALE_INVOICES_DUE',
+ SALE_INVOICE_SMS_DETAIL: 'SALE_INVOICE_SMS_DETAIL',
+ NOTIFY_SALE_INVOICE_BY_SMS: 'NOTIFY_SALE_INVOICE_BY_SMS',
BAD_DEBT: 'BAD_DEBT',
CANCEL_BAD_DEBT: 'CANCEL_BAD_DEBT',
};
@@ -103,6 +111,9 @@ const SETTING = {
SETTING_MANUAL_JOURNALS: 'SETTING_MANUAL_JOURNALS',
SETTING_ITEMS: 'SETTING_ITEMS',
SETTING_CASHFLOW: 'SETTING_CASHFLOW',
+ SETTING_SMS_NOTIFICATION: 'SETTING_SMS_NOTIFICATION',
+ SETTING_SMS_NOTIFICATIONS: 'SETTING_SMS_NOTIFICATIONS',
+ SETTING_EDIT_SMS_NOTIFICATION: 'SETTING_EDIT_SMS_NOTIFICATION',
};
const ORGANIZATIONS = {
diff --git a/src/lang/ar/index.json b/src/lang/ar/index.json
index 945c453cb..c584afa0d 100644
--- a/src/lang/ar/index.json
+++ b/src/lang/ar/index.json
@@ -1439,5 +1439,36 @@
"bad_debt.dialog.header_note": "يمكن للبائع تحميل مبلغ الفاتورة على حساب مصروفات الديون المعدومة عندما يكون من المؤكد أن الفاتورة لن يتم دفعها.",
"bad_debt.dialog.success_message":"تم شطب فاتورة البيع المقدمة بنجاح.",
"bad_debt.cancel_alert.success_message":"تم إلغاء شطب فاتورة البيع المقدمة بنجاح.",
- "bad_debt.cancel_alert.message": "هل أنت متأكد أنك تريد شطب هذه الفاتورة؟ "
+ "bad_debt.cancel_alert.message": "هل أنت متأكد أنك تريد شطب هذه الفاتورة؟ ",
+ "notify_via_sms.dialog.send_notification_to":"إرسال إشعار إلى ",
+ "notify_via_sms.dialog.message_text":"نص رسالة ",
+ "notify_via_sms.dialog.notification_type": "نوع إشعار",
+ "notify_via_sms.dialog.notify_via_sms":"إشعار عبر رسائل قصيرة",
+ "notiify_via_sms.dialog.sms_note": "ملاحظة : يمكن أن تحتوي رسالة قصيرة الواحدة على 160 حرفًا كحد أقصى. {value} سيتم استخدام وحدات الرسائل القصيرة لإرسال هذا إشعار عبر الرسائل القصيرة. ",
+ "notify_Via_sms.dialog.customer_phone_number_does_not_eixst": "رقم هاتف العميل غير موجود ، يرجى إدخال رقم هاتف للعميل. ",
+ "notify_Via_sms.dialog.customer_phone_number_invalid": "رقم هاتف العميل غير صالح ، يرجى إدخال رقم هاتف صحيح للعميل. ",
+ "notify_via_sms.dialog.phone_invalid_error_message":"لا يمكن إرسال إشعار الرسائل القصيرة ، رقم الهاتف للعميل غير صالح ، يرجى إدخال رقم صالح والمحاولة مرة أخرى.",
+ "notify_via_sms.dialog.customer_no_phone_error_message":"الزبون ليس لديه رقم هاتف.",
+ "notify_invoice_via_sms.dialog.success_message":"تم إرسال إشعار فاتورة البيع عبر الرسائل القصيرة بنجاح ",
+ "notify_estimate_via_sms.dialog.success_message":"تم إرسال إشعار العرض البيع عبر الرسائل القصيرة بنجاح ",
+ "notify_receipt_via_sms.dialog.success_message":"تم إرسال إشعار إيصال البيع عبر الرسائل القصيرة بنجاح ",
+ "notify_payment_receive_via_sms.dialog.success_message":"تم إرسال إشعار الدفع بنجاح. ",
+ "sms_integration.label":"تكامل الرسائل القصيرة",
+ "sms_integration.label.overview":"نظرة عامة",
+ "sms_integration.label.sms_messages":"الرسائل القصيرة ",
+ "sms_messages.label_mesage":"رسالة ",
+ "sms_messages.label_notification":"إشعار",
+ "sms_messages.label_auto":"تلقائي",
+ "sms_messages.label_edit_message": "تعديل الرسالة ",
+ "sms_messages.notification_switch_change_success_message":"تم تمكين إشعار الرسائل القصيرة بنجاح.",
+ "sms_message.dialog.label": "رسالة قصيرة",
+ "sms_message.dialog.sms_note": "ملاحظة : One SMS unit can contain amaximum of 160 characters. {value} SMS units will be used to send this SMS notification.",
+ "sms_message.dialog.success_message": "تم تحديث إعدادات إشعار الرسائل القصيرة بنجاح. ",
+ "sms_message.dialog.unsupported_variables_error_message": "متغيرات غير مدعومة",
+ "sms_message.dialog.message_variable_description":"{value} إشارة لاسم الشركة الحالي.",
+ "edit_message_text":"تعديل نص رسالة",
+ "enable_notification":"تفعيل الإشعارات",
+ "send_sms":"إرسال رسالة قصيرة",
+ "save_sms_message":"حفظ رسالة قصيرة"
+
}
\ No newline at end of file
diff --git a/src/lang/en/index.json b/src/lang/en/index.json
index 058cfe230..a20e1e854 100644
--- a/src/lang/en/index.json
+++ b/src/lang/en/index.json
@@ -1407,7 +1407,6 @@
"cash_flow_money_out": "Money Out",
"cash_flow_transaction.switch_item": "Transactions {value}",
"cash_flow_transaction.balance_in_bigcapital": "Balance in Bigcapital",
-
"AR_aging_summary.filter_customers.all_customers": "All customers",
"AR_aging_summary.filter_customers.all_customers.hint": "All customers, include that ones have zero-balance.",
"AR_aging_summary.filter_customers.without_zero_balance": "Customers without zero balance",
@@ -1425,8 +1424,37 @@
"bad_debt.dialog.bad_debt": "Bad debt",
"bad_debt.dialog.cancel_bad_debt": "Cancel bad debt",
"bad_debt.dialog.header_note": "The seller can charge the amount of an invoice to the bad debt expense account when it is certain that the invoice will not be paid.",
- "bad_debt.dialog.success_message":"The given sale invoice has been writte-off successfully.",
- "bad_debt.cancel_alert.success_message":"The given sale invoice has been canceled write-off successfully.",
- "bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?"
-
-}
+ "bad_debt.dialog.success_message": "The given sale invoice has been writte-off successfully.",
+ "bad_debt.cancel_alert.success_message": "The given sale invoice has been canceled write-off successfully.",
+ "bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?",
+ "notify_via_sms.dialog.send_notification_to": "Send notification to",
+ "notify_via_sms.dialog.message_text": "Message Text",
+ "notify_via_sms.dialog.notification_type": "Notification type",
+ "notify_via_sms.dialog.notify_via_sms": "Notify vis SMS",
+ "notiify_via_sms.dialog.sms_note": "Note : One SMS unit can contain a maximum of 160 characters. {value} SMS units will be used to send this SMS notification.",
+ "notify_Via_sms.dialog.customer_phone_number_does_not_eixst": "The customer phone number does not eixst, please enter a personal phone number to the customer.",
+ "notify_Via_sms.dialog.customer_phone_number_invalid": "The customer phone number is invalid, please enter a valid personal phone number to the customer.",
+ "notify_via_sms.dialog.phone_invalid_error_message": "Sms notification cannot be sent, customer personal phone number is invalid, please enter a valid one and try again.",
+ "notify_via_sms.dialog.customer_no_phone_error_message": "The customer has no phone number.",
+ "notify_invoice_via_sms.dialog.success_message": "The sale invoice sms notification has been sent successfully.",
+ "notify_estimate_via_sms.dialog.success_message": "The sale estimate sms notification has been sent successfully",
+ "notify_receipt_via_sms.dialog.success_message": "The sale receipt sms notification has been sent successfully",
+ "notify_payment_receive_via_sms.dialog.success_message": "The payment notification has been sent successfully.",
+ "sms_integration.label": "SMS Integration",
+ "sms_integration.label.overview": "Overview",
+ "sms_integration.label.sms_messages": "SMS Messages",
+ "sms_messages.label_notification": "Notification",
+ "sms_messages.label_mesage": "Message",
+ "sms_messages.label_auto": "Auto",
+ "sms_messages.label_edit_message": "Edit Message",
+ "sms_messages.notification_switch_change_success_message": "SMS notification hs been enabled successfully.",
+ "sms_message.dialog.label": "SMS message",
+ "sms_message.dialog.sms_note": "Note : One SMS unit can contain a maximum of 160 characters. {value} SMS units will be used to send this SMS notification.",
+ "sms_message.dialog.success_message": "Sms notification settings has been updated successfully.",
+ "sms_message.dialog.unsupported_variables_error_message": "Unsupported variables",
+ "sms_message.dialog.message_variable_description": "{value} References to the current company name.",
+ "edit_message_text": "Edit message text",
+ "enable_notification": "Enable notification",
+ "send_sms": "Send SMS",
+ "save_sms_message": "Save SMS Message"
+}
\ No newline at end of file
diff --git a/src/routes/preferences.js b/src/routes/preferences.js
index cc539a664..98ff2e46b 100644
--- a/src/routes/preferences.js
+++ b/src/routes/preferences.js
@@ -4,6 +4,7 @@ import Accountant from 'containers/Preferences/Accountant/Accountant';
// import Accounts from 'containers/Preferences/Accounts/Accounts';
import Currencies from 'containers/Preferences/Currencies/Currencies';
import Item from 'containers/Preferences/Item';
+import SMSIntegration from '../containers/Preferences/SMSIntegration';
import DefaultRoute from '../containers/Preferences/DefaultRoute';
const BASE_URL = '/preferences';
@@ -34,6 +35,11 @@ export default [
component: Item,
exact: true,
},
+ {
+ path: `${BASE_URL}/sms-message`,
+ component: SMSIntegration,
+ exact: true,
+ },
{
path: `${BASE_URL}/`,
component: DefaultRoute,
diff --git a/src/static/json/icons.js b/src/static/json/icons.js
index 42a191624..fc6ffe780 100644
--- a/src/static/json/icons.js
+++ b/src/static/json/icons.js
@@ -509,4 +509,11 @@ export default {
],
viewBox: '0 0 24 24',
},
+ "sms-message-preview": {
+ path: [
+ 'M8.341,375.3573H399.3271v-.0015l-390.9861-.07ZM363.2382,0H44.43A44.4508,44.4508,0,0,0,0,44.371V375.284l8.341.0016V44.371A36.0651,36.0651,0,0,1,44.43,8.33H90.7089a4.6454,4.6454,0,0,1,4.6482,4.6423v1.9718a23.8588,23.8588,0,0,0,23.8742,23.843H288.9146a23.8586,23.8586,0,0,0,23.8741-23.843V12.972A4.6456,4.6456,0,0,1,317.4372,8.33h45.801A36.0651,36.0651,0,0,1,399.3271,44.371V375.3558l8.341.0015V44.371A44.4508,44.4508,0,0,0,363.2382,0Z',
+ "M1199.9485,803.1623"
+ ],
+ viewBox: "0 0 407.6681 375.3573",
+ }
};
diff --git a/src/style/pages/NotifyConactViaSMS/NotifyConactViaSMSDialog.scss b/src/style/pages/NotifyConactViaSMS/NotifyConactViaSMSDialog.scss
new file mode 100644
index 000000000..f4e334703
--- /dev/null
+++ b/src/style/pages/NotifyConactViaSMS/NotifyConactViaSMSDialog.scss
@@ -0,0 +1,31 @@
+.dialog--notify-vis-sms {
+ width: 800px;
+
+ .bp3-dialog-body {
+ .bp3-form-group {
+ margin-bottom: 15px;
+ margin-top: 15px;
+
+ label.bp3-label {
+ margin-bottom: 3px;
+ font-size: 13px;
+ }
+ }
+
+ .form-group {
+ &--sms_message {
+ .bp3-form-content {
+ textarea {
+ width: 100%;
+ min-width: 100%;
+ font-size: 14px;
+ }
+ }
+ }
+ }
+ }
+
+ .bp3-dialog-footer {
+ padding-top: 10px;
+ }
+}
diff --git a/src/style/pages/Preferences/Page.scss b/src/style/pages/Preferences/Page.scss
index 1f996676e..d156b96c4 100644
--- a/src/style/pages/Preferences/Page.scss
+++ b/src/style/pages/Preferences/Page.scss
@@ -17,8 +17,7 @@
&__inside-content {
display: flex;
flex-direction: column;
- height: 100%;
-
+
&--tabable {
margin-left: -25px;
margin-right: -25px;
diff --git a/src/style/pages/Preferences/SMSIntegration.scss b/src/style/pages/Preferences/SMSIntegration.scss
new file mode 100644
index 000000000..d2a52edf0
--- /dev/null
+++ b/src/style/pages/Preferences/SMSIntegration.scss
@@ -0,0 +1,38 @@
+// SMS Integration.
+// ---------------------------------
+
+.preferences-page__inside-content--sms-integration {
+ .bigcapital-datatable {
+ .table {
+ .tbody {
+ .notification {
+ &__label {
+ font-weight: 500;
+ }
+
+ &__desc {
+ font-size: 13px;
+ margin-top: 3px;
+ line-height: 1.25;
+ display: block;
+ }
+ }
+
+ .sms_message.td {
+ .edit-text {
+ display: inline-block;
+ font-size: 11.5px;
+ color: #1652c8;
+ margin-left: 2px;
+ text-decoration: underline;
+ }
+ }
+ }
+ }
+ }
+ .bp3-tabs {
+ .bp3-tab-panel {
+ margin-top: 0;
+ }
+ }
+}
diff --git a/src/style/pages/SMSMessage/SMSMessage.scss b/src/style/pages/SMSMessage/SMSMessage.scss
new file mode 100644
index 000000000..bd9b46c90
--- /dev/null
+++ b/src/style/pages/SMSMessage/SMSMessage.scss
@@ -0,0 +1,29 @@
+.dialog--sms-message {
+ width: 800px;
+
+ .bp3-form-group {
+ margin-bottom: 15px;
+
+ label.bp3-label {
+ font-size: 13px;
+ margin-bottom: 6px;
+ }
+ }
+
+ .form-group {
+ &--message_text {
+ .bp3-form-content {
+ textarea {
+ width: 100%;
+ min-width: 100%;
+ min-height: 90px;
+ font-size: 14px;
+ }
+ }
+ }
+ }
+
+ .bp3-dialog-footer {
+ padding-top: 10px;
+ }
+}
diff --git a/src/utils/index.js b/src/utils/index.js
index c5f67fd32..f91986577 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -341,7 +341,7 @@ export const saveInvoke = (func, ...rest) => {
};
export const safeInvoke = (func, ...rest) => {
- return func && func(...rest);
+ func && func(...rest);
};
export const transformToForm = (obj, emptyInitialValues) => {