mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 15:50:32 +00:00
Merge branch 'feature/notify-via-SMS' into develop
This commit is contained in:
@@ -66,6 +66,7 @@ const CLASSES = {
|
|||||||
PREFERENCES_PAGE_INSIDE_CONTENT_USERS: 'preferences-page__inside-content--users',
|
PREFERENCES_PAGE_INSIDE_CONTENT_USERS: 'preferences-page__inside-content--users',
|
||||||
PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES: 'preferences-page__inside-content--currencies',
|
PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES: 'preferences-page__inside-content--currencies',
|
||||||
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
|
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',
|
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
|
|
||||||
export const ButtonLink = styled(Button)`
|
export const ButtonLink = styled.button`
|
||||||
line-height: inherit;
|
color: #0052cc;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&.bp3-small {
|
&:hover,
|
||||||
min-height: auto;
|
&:active {
|
||||||
min-width: auto;
|
text-decoration: underline;
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
&:not([class*='bp3-intent-']) {
|
|
||||||
&,
|
|
||||||
&:hover {
|
|
||||||
color: #0052cc;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
51
src/components/DataTableCells/SwitchFieldCell.js
Normal file
51
src/components/DataTableCells/SwitchFieldCell.js
Normal file
@@ -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 (
|
||||||
|
<FormGroup
|
||||||
|
intent={error ? Intent.DANGER : null}
|
||||||
|
className={classNames(Classes.FILL)}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
checked={initialValue}
|
||||||
|
minimal={true}
|
||||||
|
className="ml2"
|
||||||
|
{...switchProps}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SwitchEditableCell;
|
||||||
42
src/components/DataTableCells/TextAreaCell.js
Normal file
42
src/components/DataTableCells/TextAreaCell.js
Normal file
@@ -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 (
|
||||||
|
<FormGroup
|
||||||
|
intent={error ? Intent.DANGER : null}
|
||||||
|
className={classNames(Classes.FILL)}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
fill={true}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextAreaEditableCell;
|
||||||
@@ -6,7 +6,9 @@ import ItemsListCell from './ItemsListCell';
|
|||||||
import PercentFieldCell from './PercentFieldCell';
|
import PercentFieldCell from './PercentFieldCell';
|
||||||
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
||||||
import NumericInputCell from './NumericInputCell';
|
import NumericInputCell from './NumericInputCell';
|
||||||
import CheckBoxFieldCell from './CheckBoxFieldCell'
|
import CheckBoxFieldCell from './CheckBoxFieldCell';
|
||||||
|
import SwitchFieldCell from './SwitchFieldCell';
|
||||||
|
import TextAreaCell from './TextAreaCell';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AccountsListFieldCell,
|
AccountsListFieldCell,
|
||||||
@@ -18,5 +20,7 @@ export {
|
|||||||
DivFieldCell,
|
DivFieldCell,
|
||||||
EmptyDiv,
|
EmptyDiv,
|
||||||
NumericInputCell,
|
NumericInputCell,
|
||||||
CheckBoxFieldCell
|
CheckBoxFieldCell,
|
||||||
|
SwitchFieldCell,
|
||||||
|
TextAreaCell,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ function DialogComponent(props) {
|
|||||||
const { name, children, closeDialog, onClose } = props;
|
const { name, children, closeDialog, onClose } = props;
|
||||||
|
|
||||||
const handleClose = (event) => {
|
const handleClose = (event) => {
|
||||||
closeDialog(name)
|
closeDialog(name);
|
||||||
onClose && onClose(event);
|
onClose && onClose(event);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog {...props} onClose={handleClose}>
|
<Dialog {...props} onClose={handleClose}>
|
||||||
{ children }
|
{children}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
const DialogRoot = compose(withDialogActions)(DialogComponent);
|
||||||
withDialogActions,
|
|
||||||
)(DialogComponent);
|
export { DialogRoot as Dialog };
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Spinner, Classes } from '@blueprintjs/core';
|
import { Spinner, Classes } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default function DialogContent(props) {
|
export function DialogContent(props) {
|
||||||
const { isLoading, children } = props;
|
const { isLoading, children } = props;
|
||||||
|
|
||||||
const loadingContent = (
|
const loadingContent = (
|
||||||
|
|||||||
26
src/components/Dialog/DialogFooterActions.js
Normal file
26
src/components/Dialog/DialogFooterActions.js
Normal file
@@ -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 (
|
||||||
|
<DialogFooterActionsRoot
|
||||||
|
className={Classes.DIALOG_FOOTER_ACTIONS}
|
||||||
|
alignment={alignment}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogFooterActionsRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -5,7 +5,7 @@ function LoadingContent() {
|
|||||||
return (<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>);
|
return (<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DialogSuspense({
|
export function DialogSuspense({
|
||||||
children
|
children
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
6
src/components/Dialog/index.js
Normal file
6
src/components/Dialog/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export * from './Dialog';
|
||||||
|
export * from './DialogFooterActions';
|
||||||
|
export * from './DialogSuspense';
|
||||||
|
export * from './DialogContent';
|
||||||
@@ -20,6 +20,11 @@ import ReceiptPdfPreviewDialog from '../containers/Dialogs/ReceiptPdfPreviewDial
|
|||||||
import MoneyInDialog from '../containers/Dialogs/MoneyInDialog';
|
import MoneyInDialog from '../containers/Dialogs/MoneyInDialog';
|
||||||
import MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
|
import MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
|
||||||
import BadDebtDialog from '../containers/Dialogs/BadDebtDialog';
|
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.
|
* Dialogs container.
|
||||||
@@ -45,7 +50,14 @@ export default function DialogsContainer() {
|
|||||||
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
||||||
<MoneyInDialog dialogName={'money-in'} />
|
<MoneyInDialog dialogName={'money-in'} />
|
||||||
<MoneyOutDialog dialogName={'money-out'} />
|
<MoneyOutDialog dialogName={'money-out'} />
|
||||||
|
|
||||||
|
<NotifyInvoiceViaSMSDialog dialogName={'notify-invoice-via-sms'} />
|
||||||
|
<NotifyReceiptViaSMSDialog dialogName={'notify-receipt-via-sms'} />
|
||||||
|
<NotifyEstimateViaSMSDialog dialogName={'notify-estimate-via-sms'} />
|
||||||
|
<NotifyPaymentReceiveViaSMSDialog dialogName={'notify-payment-via-sms'} />
|
||||||
|
|
||||||
<BadDebtDialog dialogName={'write-off-bad-debt'} />
|
<BadDebtDialog dialogName={'write-off-bad-debt'} />
|
||||||
|
<SMSMessageDialog dialogName={'sms-message-form'} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/components/MoreMenutItems.js
Normal file
36
src/components/MoreMenutItems.js
Normal file
@@ -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 (
|
||||||
|
<Popover
|
||||||
|
minimal={true}
|
||||||
|
content={
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
onClick={onNotifyViaSMS}
|
||||||
|
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
modifiers={{
|
||||||
|
offset: { offset: '0, 4' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoreMenuItems;
|
||||||
@@ -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 }) => (
|
|
||||||
<MenuItem text={item.name} label={item.label} onClick={handleClick} />
|
|
||||||
);
|
|
||||||
const handleMenuSelect = (type) => {
|
|
||||||
onItemSelect && onItemSelect(type);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
items={items}
|
|
||||||
itemRenderer={itemsRenderer}
|
|
||||||
onItemSelect={handleMenuSelect}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: true,
|
|
||||||
position: Position.BOTTOM_LEFT,
|
|
||||||
interactionKind: PopoverInteractionKind.CLICK,
|
|
||||||
modifiers: {
|
|
||||||
offset: { offset: '0, 4' },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
filterable={false}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
text={text}
|
|
||||||
icon={<Icon icon={'more-vert'} iconSize={16} />}
|
|
||||||
minimal={true}
|
|
||||||
{...buttonProps}
|
|
||||||
/>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MoreVertMenutItems;
|
|
||||||
46
src/components/SMSPreview/index.js
Normal file
46
src/components/SMSPreview/index.js
Normal file
@@ -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 (
|
||||||
|
<SMSMessagePreviewBase>
|
||||||
|
<Icon
|
||||||
|
icon={'sms-message-preview'}
|
||||||
|
width={iconWidth}
|
||||||
|
height={iconHeight}
|
||||||
|
color={iconColor}
|
||||||
|
/>
|
||||||
|
<SMSMessageText>{message}</SMSMessageText>
|
||||||
|
</SMSMessagePreviewBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
`;
|
||||||
@@ -23,9 +23,6 @@ import AccountsSelectList from './AccountsSelectList';
|
|||||||
import AccountsTypesSelect from './AccountsTypesSelect';
|
import AccountsTypesSelect from './AccountsTypesSelect';
|
||||||
import LoadingIndicator from './LoadingIndicator';
|
import LoadingIndicator from './LoadingIndicator';
|
||||||
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
|
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 InputPrependButton from './Forms/InputPrependButton';
|
||||||
import CategoriesSelectList from './CategoriesSelectList';
|
import CategoriesSelectList from './CategoriesSelectList';
|
||||||
import Row from './Grid/Row';
|
import Row from './Grid/Row';
|
||||||
@@ -61,8 +58,9 @@ import Card from './Card';
|
|||||||
import AvaterCell from './AvaterCell';
|
import AvaterCell from './AvaterCell';
|
||||||
|
|
||||||
import { ItemsMultiSelect } from './Items';
|
import { ItemsMultiSelect } from './Items';
|
||||||
import MoreVertMenutItems from './MoreVertMenutItems';
|
import MoreMenuItems from './MoreMenutItems';
|
||||||
|
|
||||||
|
export * from './Dialog';
|
||||||
export * from './Menu';
|
export * from './Menu';
|
||||||
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
||||||
export * from './AdvancedFilter/AdvancedFilterPopover';
|
export * from './AdvancedFilter/AdvancedFilterPopover';
|
||||||
@@ -83,10 +81,11 @@ export * from './MultiSelectTaggable';
|
|||||||
export * from './Utils/FormatNumber';
|
export * from './Utils/FormatNumber';
|
||||||
export * from './Utils/FormatDate';
|
export * from './Utils/FormatDate';
|
||||||
export * from './BankAccounts';
|
export * from './BankAccounts';
|
||||||
export * from './IntersectionObserver'
|
export * from './IntersectionObserver';
|
||||||
export * from './Datatable/CellForceWidth';
|
export * from './Datatable/CellForceWidth';
|
||||||
export * from './Button';
|
export * from './Button';
|
||||||
export * from './IntersectionObserver';
|
export * from './IntersectionObserver';
|
||||||
|
export * from './SMSPreview';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
@@ -120,9 +119,6 @@ export {
|
|||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
DashboardActionViewsList,
|
DashboardActionViewsList,
|
||||||
AppToaster,
|
AppToaster,
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogSuspense,
|
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
CategoriesSelectList,
|
CategoriesSelectList,
|
||||||
Col,
|
Col,
|
||||||
@@ -158,5 +154,5 @@ export {
|
|||||||
ItemsMultiSelect,
|
ItemsMultiSelect,
|
||||||
Card,
|
Card,
|
||||||
AvaterCell,
|
AvaterCell,
|
||||||
MoreVertMenutItems,
|
MoreMenuItems,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
text: <T id={'general'}/>,
|
text: <T id={'general'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/general',
|
href: '/preferences/general',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'users'}/>,
|
text: <T id={'users'} />,
|
||||||
href: '/preferences/users',
|
href: '/preferences/users',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'currencies'}/>,
|
text: <T id={'currencies'} />,
|
||||||
|
|
||||||
href: '/preferences/currencies',
|
href: '/preferences/currencies',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'accountant'}/>,
|
text: <T id={'accountant'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/accountant',
|
href: '/preferences/accountant',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'items'}/>,
|
text: <T id={'items'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/items',
|
href: '/preferences/items',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'sms_integration.label'} />,
|
||||||
|
disabled: false,
|
||||||
|
href: '/preferences/sms-message',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NotifyEstimateViaSMSFormProvider } from './NotifyEstimateViaSMSFormProvider';
|
||||||
|
import NotifyEstimateViaSMSForm from './NotifyEstimateViaSMSForm';
|
||||||
|
|
||||||
|
export default function NotifyEstimateViaSMSDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
estimate,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<NotifyEstimateViaSMSFormProvider
|
||||||
|
estimateId={estimate}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<NotifyEstimateViaSMSForm />
|
||||||
|
</NotifyEstimateViaSMSFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<NotifyViaSMSForm
|
||||||
|
initialValues={initialValues}
|
||||||
|
notificationTypes={notificationType}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(NotifyEstimateViaSMSForm);
|
||||||
@@ -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 (
|
||||||
|
<DialogContent isLoading={isEstimateSMSDetailLoading}>
|
||||||
|
<NotifyEstimateViaSMSContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useEstimateViaSMSContext = () =>
|
||||||
|
React.useContext(NotifyEstimateViaSMSContext);
|
||||||
|
|
||||||
|
export { NotifyEstimateViaSMSFormProvider, useEstimateViaSMSContext };
|
||||||
36
src/containers/Dialogs/NotifyEstimateViaSMSDialog/index.js
Normal file
36
src/containers/Dialogs/NotifyEstimateViaSMSDialog/index.js
Normal file
@@ -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 (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--notify-vis-sms'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<NotifyEstimateViaSMSDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
estimate={estimateId}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogRedux())(NotifyEstimateViaSMSDialog);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { NotifyInvoiceViaSMSFormProvider } from './NotifyInvoiceViaSMSFormProvider';
|
||||||
|
import NotifyInvoiceViaSMSForm from './NotifyInvoiceViaSMSForm';
|
||||||
|
|
||||||
|
export default function NotifyInvoiceViaSMSDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
invoiceId,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<NotifyInvoiceViaSMSFormProvider
|
||||||
|
invoiceId={invoiceId}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<NotifyInvoiceViaSMSForm />
|
||||||
|
</NotifyInvoiceViaSMSFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<NotifyViaSMSForm
|
||||||
|
initialValues={initialValues}
|
||||||
|
notificationTypes={notificationTypes}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
onValuesChange={handleValuesChange}
|
||||||
|
calloutCodes={calloutCode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(NotifyInvoiceViaSMSForm);
|
||||||
@@ -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 (
|
||||||
|
<DialogContent isLoading={isInvoiceSMSDetailLoading}>
|
||||||
|
<NotifyInvoiceViaSMSContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useNotifyInvoiceViaSMSContext = () =>
|
||||||
|
React.useContext(NotifyInvoiceViaSMSContext);
|
||||||
|
|
||||||
|
export { NotifyInvoiceViaSMSFormProvider, useNotifyInvoiceViaSMSContext };
|
||||||
36
src/containers/Dialogs/NotifyInvoiceViaSMSDialog/index.js
Normal file
36
src/containers/Dialogs/NotifyInvoiceViaSMSDialog/index.js
Normal file
@@ -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 (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--notify-vis-sms'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<NotifyInvoiceViaSMSDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
invoiceId={invoiceId}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogRedux())(NotifyInvoiceViaSMSDialog);
|
||||||
@@ -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 (
|
||||||
|
<DialogContent isLoading={isPaymentReceiveSMSDetailLoading}>
|
||||||
|
<NotifyPaymentReceiveViaSMSContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useNotifyPaymentReceiveViaSMSContext = () =>
|
||||||
|
React.useContext(NotifyPaymentReceiveViaSMSContext);
|
||||||
|
|
||||||
|
export {
|
||||||
|
NotifyPaymentReceiveViaFormProvider,
|
||||||
|
useNotifyPaymentReceiveViaSMSContext,
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { NotifyPaymentReceiveViaFormProvider } from './NotifyPaymentReceiveViaFormProvider';
|
||||||
|
import NotifyPaymentReceiveViaSMSForm from './NotifyPaymentReceiveViaSMSForm';
|
||||||
|
|
||||||
|
export default function NotifyPaymentReceiveViaSMSContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
paymentReceive,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<NotifyPaymentReceiveViaFormProvider
|
||||||
|
paymentReceiveId={paymentReceive}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<NotifyPaymentReceiveViaSMSForm />
|
||||||
|
</NotifyPaymentReceiveViaFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<NotifyViaSMSForm
|
||||||
|
initialValues={initialValues}
|
||||||
|
notificationTypes={notificationType}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogActions)(NotifyPaymentReceiveViaSMSForm);
|
||||||
@@ -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 (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--notify-vis-sms'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<NotifyPaymentReceiveViaSMSDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
paymentReceive={paymentReceiveId}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogRedux())(NotifyPaymentReciveViaSMSDialog);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { NotifyReceiptViaSMSFormProvider } from './NotifyReceiptViaSMSFormProvider';
|
||||||
|
import NotifyReceiptViaSMSForm from './NotifyReceiptViaSMSForm';
|
||||||
|
|
||||||
|
export default function NotifyReceiptViaSMSDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
receipt,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<NotifyReceiptViaSMSFormProvider
|
||||||
|
receiptId={receipt}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<NotifyReceiptViaSMSForm />
|
||||||
|
</NotifyReceiptViaSMSFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<NotifyViaSMSForm
|
||||||
|
initialValues={initialValues}
|
||||||
|
notificationTypes={notificationType}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(NotifyReceiptViaSMSForm);
|
||||||
@@ -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 (
|
||||||
|
<DialogContent isLoading={isReceiptSMSDetailLoading}>
|
||||||
|
<NotifyReceiptViaSMSContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useNotifyReceiptViaSMSContext = () =>
|
||||||
|
React.useContext(NotifyReceiptViaSMSContext);
|
||||||
|
|
||||||
|
export { NotifyReceiptViaSMSFormProvider, useNotifyReceiptViaSMSContext };
|
||||||
35
src/containers/Dialogs/NotifyReceiptViaSMSDialog/index.js
Normal file
35
src/containers/Dialogs/NotifyReceiptViaSMSDialog/index.js
Normal file
@@ -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 (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--notify-vis-sms'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<NotifyReceiptViaSMSDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
receipt={receiptId}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogRedux())(NotifyReceiptViaSMSDialog);
|
||||||
@@ -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 (
|
||||||
|
<SMSMessageDialogProvider
|
||||||
|
dialogName={dialogName}
|
||||||
|
notificationkey={notificationkey}
|
||||||
|
>
|
||||||
|
<SMSMessageForm />
|
||||||
|
</SMSMessageDialogProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<DialogContent isLoading={isSMSNotificationLoading}>
|
||||||
|
<SMSMessageDialogContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSMSMessageDialogContext = () =>
|
||||||
|
React.useContext(SMSMessageDialogContext);
|
||||||
|
|
||||||
|
export { SMSMessageDialogProvider, useSMSMessageDialogContext };
|
||||||
80
src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.js
Normal file
80
src/containers/Dialogs/SMSMessageDialog/SMSMessageForm.js
Normal file
@@ -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 (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateSMSMessageFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={SMSMessageFormContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(SMSMessageForm);
|
||||||
@@ -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;
|
||||||
109
src/containers/Dialogs/SMSMessageDialog/SMSMessageFormContent.js
Normal file
109
src/containers/Dialogs/SMSMessageDialog/SMSMessageFormContent.js
Normal file
@@ -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 (
|
||||||
|
<Form>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<FormContent>
|
||||||
|
<FormFields>
|
||||||
|
<SMSMessageFormFields />
|
||||||
|
<SMSMessageVariables>
|
||||||
|
{messageVariables.map(({ variable, description }) => (
|
||||||
|
<MessageVariable>
|
||||||
|
<strong>{`{${variable}}`}</strong> {description}
|
||||||
|
</MessageVariable>
|
||||||
|
))}
|
||||||
|
</SMSMessageVariables>
|
||||||
|
</FormFields>
|
||||||
|
|
||||||
|
<FormPreview>
|
||||||
|
<SMSMessagePreviewSection />
|
||||||
|
</FormPreview>
|
||||||
|
</FormContent>
|
||||||
|
</div>
|
||||||
|
<SMSMessageFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS Message preview section.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function SMSMessagePreviewSection() {
|
||||||
|
const {
|
||||||
|
values: { message_text: message },
|
||||||
|
} = useFormikContext();
|
||||||
|
|
||||||
|
const messagesUnits = getSMSUnits(message);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SMSPreviewSectionRoot>
|
||||||
|
<SMSMessagePreview message={message} />
|
||||||
|
<SMSPreviewSectionNote>
|
||||||
|
{intl.formatHTMLMessage(
|
||||||
|
{ id: 'sms_message.dialog.sms_note' },
|
||||||
|
{
|
||||||
|
value: messagesUnits,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</SMSPreviewSectionNote>
|
||||||
|
</SMSPreviewSectionRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
`;
|
||||||
@@ -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 (
|
||||||
|
<div>
|
||||||
|
{/* ----------- Message Text ----------- */}
|
||||||
|
<FastField name={'message_text'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'notify_via_sms.dialog.message_text'} />}
|
||||||
|
className={'form-group--message_text'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={
|
||||||
|
<>
|
||||||
|
<ErrorMessage name={'message_text'} />
|
||||||
|
<ResetButton
|
||||||
|
minimal={true}
|
||||||
|
small={true}
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
onClick={handleBtnClick}
|
||||||
|
>
|
||||||
|
Reset to default message.
|
||||||
|
</ResetButton>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResetButton = styled(Button)`
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
@@ -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 (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<DialogFooterActions alignment={'left'}>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<T id={'save_sms_message'} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
</DialogFooterActions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(SMSMessageFormFloatingActions);
|
||||||
39
src/containers/Dialogs/SMSMessageDialog/index.js
Normal file
39
src/containers/Dialogs/SMSMessageDialog/index.js
Normal file
@@ -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 (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={intl.get('sms_message.dialog.label')}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--sms-message'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<SMSMessageDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
notificationkey={notificationkey}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogRedux())(SMSMessageDialog);
|
||||||
19
src/containers/Dialogs/SMSMessageDialog/utils.js
Normal file
19
src/containers/Dialogs/SMSMessageDialog/utils.js
Normal file
@@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -15,7 +15,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
|||||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
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';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -51,6 +51,10 @@ function EstimateDetailActionsBar({
|
|||||||
const handlePrintEstimate = () => {
|
const handlePrintEstimate = () => {
|
||||||
openDialog('estimate-pdf-preview', { estimateId });
|
openDialog('estimate-pdf-preview', { estimateId });
|
||||||
};
|
};
|
||||||
|
// Handle notify via SMS.
|
||||||
|
const handleNotifyViaSMS = () => {
|
||||||
|
openDialog('notify-estimate-via-sms', { estimateId });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -75,6 +79,12 @@ function EstimateDetailActionsBar({
|
|||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClick={handleDeleteEstimate}
|
onClick={handleDeleteEstimate}
|
||||||
/>
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
<MoreMenuItems
|
||||||
|
payload={{
|
||||||
|
onNotifyViaSMS: handleNotifyViaSMS,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,28 +6,17 @@ import {
|
|||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
Classes,
|
Classes,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
Popover,
|
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
MenuItem,
|
|
||||||
Menu,
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||||
import { moreVertOptions } from '../../../common/moreVertOptions';
|
|
||||||
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
import {
|
import { If, Icon, FormattedMessage as T } from 'components';
|
||||||
If,
|
|
||||||
Icon,
|
|
||||||
FormattedMessage as T,
|
|
||||||
// MoreVertMenutItems,
|
|
||||||
} from 'components';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -76,6 +65,10 @@ function InvoiceDetailActionsBar({
|
|||||||
const handleBadDebtInvoice = () => {
|
const handleBadDebtInvoice = () => {
|
||||||
openDialog('write-off-bad-debt', { invoiceId });
|
openDialog('write-off-bad-debt', { invoiceId });
|
||||||
};
|
};
|
||||||
|
// Handle notify via SMS.
|
||||||
|
const handleNotifyViaSMS = () => {
|
||||||
|
openDialog('notify-invoice-via-sms', { invoiceId });
|
||||||
|
};
|
||||||
|
|
||||||
// Handle cancele write-off invoice.
|
// Handle cancele write-off invoice.
|
||||||
const handleCancelBadDebtInvoice = () => {
|
const handleCancelBadDebtInvoice = () => {
|
||||||
@@ -116,9 +109,11 @@ function InvoiceDetailActionsBar({
|
|||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
<BadDebtMenuItem
|
<BadDebtMenuItem
|
||||||
invoice={invoice}
|
payload={{
|
||||||
onAlert={handleCancelBadDebtInvoice}
|
onBadDebt: handleBadDebtInvoice,
|
||||||
onDialog={handleBadDebtInvoice}
|
onCancelBadDebt: handleCancelBadDebtInvoice,
|
||||||
|
onNotifyViaSMS: handleNotifyViaSMS,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Icon, FormattedMessage as T, Choose } from 'components';
|
import { Icon, FormattedMessage as T, Choose } from 'components';
|
||||||
import { FormatNumberCell } from '../../../components';
|
import { FormatNumberCell } from '../../../components';
|
||||||
|
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve invoice readonly details table columns.
|
* 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 (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
minimal={true}
|
minimal={true}
|
||||||
@@ -73,16 +78,20 @@ export const BadDebtMenuItem = ({ invoice, onDialog, onAlert }) => {
|
|||||||
<Choose.When condition={!invoice.is_writtenoff}>
|
<Choose.When condition={!invoice.is_writtenoff}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={<T id={'bad_debt.dialog.bad_debt'} />}
|
text={<T id={'bad_debt.dialog.bad_debt'} />}
|
||||||
onClick={onDialog}
|
onClick={onBadDebt}
|
||||||
/>
|
/>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When condition={invoice.is_writtenoff}>
|
<Choose.When condition={invoice.is_writtenoff}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={onAlert}
|
onClick={onCancelBadDebt}
|
||||||
text={<T id={'bad_debt.dialog.cancel_bad_debt'} />}
|
text={<T id={'bad_debt.dialog.cancel_bad_debt'} />}
|
||||||
/>
|
/>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
</Choose>
|
</Choose>
|
||||||
|
<MenuItem
|
||||||
|
onClick={onNotifyViaSMS}
|
||||||
|
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||||
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
|||||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
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';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -29,6 +29,9 @@ function PaymentReceiveActionsBar({
|
|||||||
|
|
||||||
// #withDrawerActions
|
// #withDrawerActions
|
||||||
closeDrawer,
|
closeDrawer,
|
||||||
|
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -46,6 +49,11 @@ function PaymentReceiveActionsBar({
|
|||||||
openAlert('payment-receive-delete', { paymentReceiveId });
|
openAlert('payment-receive-delete', { paymentReceiveId });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle notify via SMS.
|
||||||
|
const handleNotifyViaSMS = () => {
|
||||||
|
openDialog('notify-payment-via-sms', { paymentReceiveId });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -63,6 +71,12 @@ function PaymentReceiveActionsBar({
|
|||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClick={handleDeletePaymentReceive}
|
onClick={handleDeletePaymentReceive}
|
||||||
/>
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
<MoreMenuItems
|
||||||
|
payload={{
|
||||||
|
onNotifyViaSMS: handleNotifyViaSMS,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
|||||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
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 { useReceiptDetailDrawerContext } from './ReceiptDetailDrawerProvider';
|
||||||
|
|
||||||
import { safeCallback, compose } from 'utils';
|
import { safeCallback, compose } from 'utils';
|
||||||
@@ -46,6 +46,11 @@ function ReceiptDetailActionBar({
|
|||||||
const onPrintReceipt = () => {
|
const onPrintReceipt = () => {
|
||||||
openDialog('receipt-pdf-preview', { receiptId });
|
openDialog('receipt-pdf-preview', { receiptId });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle notify via SMS.
|
||||||
|
const handleNotifyViaSMS = () => {
|
||||||
|
openDialog('notify-receipt-via-sms', { receiptId });
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -69,6 +74,12 @@ function ReceiptDetailActionBar({
|
|||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClick={safeCallback(onDeleteReceipt)}
|
onClick={safeCallback(onDeleteReceipt)}
|
||||||
/>
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
<MoreMenuItems
|
||||||
|
payload={{
|
||||||
|
onNotifyViaSMS: handleNotifyViaSMS,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
);
|
);
|
||||||
|
|||||||
156
src/containers/NotifyViaSMS/NotifyViaSMSForm.js
Normal file
156
src/containers/NotifyViaSMS/NotifyViaSMSForm.js
Normal file
@@ -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 (
|
||||||
|
<SMSPreviewSectionRoot>
|
||||||
|
<SMSMessagePreview message={sms_message} />
|
||||||
|
<SMSPreviewSectionNote>
|
||||||
|
{intl.formatHTMLMessage(
|
||||||
|
{ id: 'notiify_via_sms.dialog.sms_note' },
|
||||||
|
{
|
||||||
|
value: messagesUnits,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</SMSPreviewSectionNote>
|
||||||
|
</SMSPreviewSectionRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (
|
||||||
|
<Formik
|
||||||
|
enableReinitialize={true}
|
||||||
|
validationSchema={CreateNotifyViaSMSFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<NotifyContent>
|
||||||
|
<NotifyFieldsSection>
|
||||||
|
<NotifyViaSMSAlerts calloutCodes={calloutCodes} />
|
||||||
|
<NotifyViaSMSFormFields
|
||||||
|
notificationTypes={formattedNotificationTypes}
|
||||||
|
/>
|
||||||
|
</NotifyFieldsSection>
|
||||||
|
|
||||||
|
<SMSMessagePreviewSection />
|
||||||
|
</NotifyContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NotifyViaSMSFormFloatingActions onCancel={onCancel} />
|
||||||
|
<NotifyObserveValuesChange onChange={onValuesChange} />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the values change of notify form.
|
||||||
|
*/
|
||||||
|
function NotifyObserveValuesChange({ onChange }) {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
// Handle the form change observe.
|
||||||
|
const handleChange = () => {
|
||||||
|
safeInvoke(onChange, values);
|
||||||
|
};
|
||||||
|
return <FormObserver values={values} onChange={handleChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify via SMS form alerts.
|
||||||
|
*/
|
||||||
|
function NotifyViaSMSAlerts({ calloutCodes }) {
|
||||||
|
return [
|
||||||
|
includes(calloutCodes, 100) && (
|
||||||
|
<Callout icon={null} intent={Intent.DANGER}>
|
||||||
|
{intl.get('notify_Via_sms.dialog.customer_phone_number_does_not_eixst')}
|
||||||
|
</Callout>
|
||||||
|
),
|
||||||
|
includes(calloutCodes, 200) && (
|
||||||
|
<Callout icon={null} intent={Intent.DANGER}>
|
||||||
|
{intl.get('notify_Via_sms.dialog.customer_phone_number_invalid')}
|
||||||
|
</Callout>
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
`;
|
||||||
11
src/containers/NotifyViaSMS/NotifyViaSMSForm.schema.js
Normal file
11
src/containers/NotifyViaSMS/NotifyViaSMSForm.schema.js
Normal file
@@ -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;
|
||||||
86
src/containers/NotifyViaSMS/NotifyViaSMSFormFields.js
Normal file
86
src/containers/NotifyViaSMS/NotifyViaSMSFormFields.js
Normal file
@@ -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 (
|
||||||
|
<NotifyViaSMSFormFieldsRoot>
|
||||||
|
<FastField name={'notification_key'}>
|
||||||
|
{({ form, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'notify_via_sms.dialog.notification_type'} />}
|
||||||
|
className={classNames(CLASSES.FILL)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'customer_name'} />}
|
||||||
|
>
|
||||||
|
<ListSelect
|
||||||
|
items={notificationTypes}
|
||||||
|
selectedItemProp={'key'}
|
||||||
|
selectedItem={'details'}
|
||||||
|
textProp={'label'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
filterable={false}
|
||||||
|
onItemSelect={(notification) => {
|
||||||
|
form.setFieldValue('notification_key', notification.key);
|
||||||
|
}}
|
||||||
|
disabled={notificationTypes.length < 2}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
{/* ----------- Send Notification to ----------- */}
|
||||||
|
<FastField name={'customer_name'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'notify_via_sms.dialog.send_notification_to'} />}
|
||||||
|
className={classNames('form-group--customer-name', CLASSES.FILL)}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'customer_name'} />}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
disabled={true}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
{/* ----------- Phone number ----------- */}
|
||||||
|
<FastField name={'customer_phone_number'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'phone_number'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="customer_phone_number" />}
|
||||||
|
className={classNames(
|
||||||
|
'form-group--customer_phone_number',
|
||||||
|
CLASSES.FILL,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
disabled={true}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</NotifyViaSMSFormFieldsRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const NotifyViaSMSFormFieldsRoot = styled.div``;
|
||||||
@@ -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 (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<DialogFooterActions alignment={'left'}>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '110px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<T id={'send_sms'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={handleCancelBtnClick}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
</DialogFooterActions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/containers/NotifyViaSMS/utils.js
Normal file
22
src/containers/NotifyViaSMS/utils.js
Normal file
@@ -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);
|
||||||
|
};
|
||||||
@@ -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 (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
|
||||||
|
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SMSIntegrationContext.Provider value={provider} {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSMSIntegrationContext = () => React.useContext(SMSIntegrationContext);
|
||||||
|
|
||||||
|
export { SMSIntegrationProvider, useSMSIntegrationContext };
|
||||||
@@ -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 (
|
||||||
|
<div className={classNames(CLASSES.CARD)}>
|
||||||
|
<div className={classNames(CLASSES.PREFERENCES_PAGE_TABS)}>
|
||||||
|
<Tabs animate={true} defaultSelectedTabId={'sms_messages'}>
|
||||||
|
<Tab
|
||||||
|
id="overview"
|
||||||
|
title={intl.get('sms_integration.label.overview')}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
id="sms_messages"
|
||||||
|
title={intl.get('sms_integration.label.sms_messages')}
|
||||||
|
panel={<SMSMessagesDataTable />}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDashboardActions)(SMSIntegrationTabs);
|
||||||
@@ -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 (
|
||||||
|
<SMSNotificationsTable
|
||||||
|
columns={columns}
|
||||||
|
data={notifications}
|
||||||
|
loading={isSMSNotificationsLoading}
|
||||||
|
progressBarLoading={isSMSNotificationsLoading}
|
||||||
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
|
noInitialFetch={true}
|
||||||
|
ContextMenu={ActionsMenu}
|
||||||
|
payload={{
|
||||||
|
onEditMessageText: handleEditMessageText,
|
||||||
|
onEnableNotification: handleEnableNotification,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(SMSMessagesDataTable);
|
||||||
|
|
||||||
|
const SMSNotificationsTable = styled(DataTable)`
|
||||||
|
.table .tbody .tr .td {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.table .tbody .td {
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
137
src/containers/Preferences/SMSIntegration/components.js
Normal file
137
src/containers/Preferences/SMSIntegration/components.js
Normal file
@@ -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 (
|
||||||
|
<span className="notification">
|
||||||
|
<NotificationLabel>{row.notification_label}</NotificationLabel>
|
||||||
|
<NotificationDescription>
|
||||||
|
{row.notification_description}
|
||||||
|
</NotificationDescription>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS notification message cell.
|
||||||
|
*/
|
||||||
|
export const SMSMessageCell = ({
|
||||||
|
payload: { onEditMessageText },
|
||||||
|
row: { original },
|
||||||
|
}) => (
|
||||||
|
<div>
|
||||||
|
<MessageBox>{original.sms_message}</MessageBox>
|
||||||
|
<MessageBoxActions>
|
||||||
|
<Button
|
||||||
|
minimal={true}
|
||||||
|
small={true}
|
||||||
|
intent={Intent.NONE}
|
||||||
|
onClick={() => safeInvoke(onEditMessageText, original)}
|
||||||
|
>
|
||||||
|
{intl.get('sms_messages.label_edit_message')}
|
||||||
|
</Button>
|
||||||
|
</MessageBoxActions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context menu of SMS notification messages.
|
||||||
|
*/
|
||||||
|
export function ActionsMenu({
|
||||||
|
payload: { onEditMessageText, onEnableNotification },
|
||||||
|
row: { original },
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('edit_message_text')}
|
||||||
|
onClick={safeInvoke(onEditMessageText, original)}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('enable_notification')}
|
||||||
|
onClick={safeInvoke(onEnableNotification, original)}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
15
src/containers/Preferences/SMSIntegration/index.js
Normal file
15
src/containers/Preferences/SMSIntegration/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { SMSIntegrationProvider } from './SMSIntegrationProvider';
|
||||||
|
import SMSIntegrationTabs from './SMSIntegrationTabs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS SMS Integration
|
||||||
|
*/
|
||||||
|
export default function SMSIntegration() {
|
||||||
|
return (
|
||||||
|
<SMSIntegrationProvider>
|
||||||
|
<SMSIntegrationTabs />
|
||||||
|
</SMSIntegrationProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ export function ActionsMenu({
|
|||||||
*/
|
*/
|
||||||
function StatusAccessor(user) {
|
function StatusAccessor(user) {
|
||||||
return !user.is_invite_accepted ? (
|
return !user.is_invite_accepted ? (
|
||||||
<Tag minimal={true}>
|
<Tag minimal={true} >
|
||||||
<T id={'inviting'} />
|
<T id={'inviting'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
) : user.active ? (
|
) : user.active ? (
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ const commonInvalidateQueries = (queryClient) => {
|
|||||||
|
|
||||||
// Invalidate the financial reports.
|
// Invalidate the financial reports.
|
||||||
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
|
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.
|
// Customers response selector.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -219,14 +219,53 @@ export function useCancelBadDebt(props) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const apiRequest = useApiRequest();
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
return useMutation((id) => apiRequest.post(`sales/invoices/${id}/writeoff/cancel`), {
|
return useMutation(
|
||||||
onSuccess: (res, id) => {
|
(id) => apiRequest.post(`sales/invoices/${id}/writeoff/cancel`),
|
||||||
// Invalidate
|
{
|
||||||
queryClient.invalidateQueries([t.CANCEL_BAD_DEBT, id]);
|
onSuccess: (res, id) => {
|
||||||
|
// Invalidate
|
||||||
|
queryClient.invalidateQueries([t.CANCEL_BAD_DEBT, id]);
|
||||||
|
|
||||||
// Common invalidate queries.
|
// Common invalidate queries.
|
||||||
commonInvalidateQueries(queryClient);
|
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,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useMutation, useQueryClient } from 'react-query';
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
import { useRequestQuery } from '../useQueryRequest';
|
import { useRequestQuery } from '../useQueryRequest';
|
||||||
import useApiRequest from '../useRequest';
|
import useApiRequest from '../useRequest';
|
||||||
@@ -123,3 +122,61 @@ export function useSettingCashFlow(props) {
|
|||||||
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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,11 +51,15 @@ const ITEMS = {
|
|||||||
const SALE_ESTIMATES = {
|
const SALE_ESTIMATES = {
|
||||||
SALE_ESTIMATES: 'SALE_ESTIMATES',
|
SALE_ESTIMATES: 'SALE_ESTIMATES',
|
||||||
SALE_ESTIMATE: 'SALE_ESTIMATE',
|
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 = {
|
const SALE_RECEIPTS = {
|
||||||
SALE_RECEIPTS: 'SALE_RECEIPTS',
|
SALE_RECEIPTS: 'SALE_RECEIPTS',
|
||||||
SALE_RECEIPT: 'SALE_RECEIPT',
|
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 = {
|
const INVENTORY_ADJUSTMENTS = {
|
||||||
@@ -79,12 +83,16 @@ const PAYMENT_RECEIVES = {
|
|||||||
PAYMENT_RECEIVE: 'PAYMENT_RECEIVE',
|
PAYMENT_RECEIVE: 'PAYMENT_RECEIVE',
|
||||||
PAYMENT_RECEIVE_NEW_ENTRIES: 'PAYMENT_RECEIVE_NEW_ENTRIES',
|
PAYMENT_RECEIVE_NEW_ENTRIES: 'PAYMENT_RECEIVE_NEW_ENTRIES',
|
||||||
PAYMENT_RECEIVE_EDIT_PAGE: 'PAYMENT_RECEIVE_EDIT_PAGE',
|
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 = {
|
const SALE_INVOICES = {
|
||||||
SALE_INVOICES: 'SALE_INVOICES',
|
SALE_INVOICES: 'SALE_INVOICES',
|
||||||
SALE_INVOICE: 'SALE_INVOICE',
|
SALE_INVOICE: 'SALE_INVOICE',
|
||||||
SALE_INVOICES_DUE: 'SALE_INVOICES_DUE',
|
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',
|
BAD_DEBT: 'BAD_DEBT',
|
||||||
CANCEL_BAD_DEBT: 'CANCEL_BAD_DEBT',
|
CANCEL_BAD_DEBT: 'CANCEL_BAD_DEBT',
|
||||||
};
|
};
|
||||||
@@ -103,6 +111,9 @@ const SETTING = {
|
|||||||
SETTING_MANUAL_JOURNALS: 'SETTING_MANUAL_JOURNALS',
|
SETTING_MANUAL_JOURNALS: 'SETTING_MANUAL_JOURNALS',
|
||||||
SETTING_ITEMS: 'SETTING_ITEMS',
|
SETTING_ITEMS: 'SETTING_ITEMS',
|
||||||
SETTING_CASHFLOW: 'SETTING_CASHFLOW',
|
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 = {
|
const ORGANIZATIONS = {
|
||||||
|
|||||||
@@ -1439,5 +1439,36 @@
|
|||||||
"bad_debt.dialog.header_note": "يمكن للبائع تحميل مبلغ الفاتورة على حساب مصروفات الديون المعدومة عندما يكون من المؤكد أن الفاتورة لن يتم دفعها.",
|
"bad_debt.dialog.header_note": "يمكن للبائع تحميل مبلغ الفاتورة على حساب مصروفات الديون المعدومة عندما يكون من المؤكد أن الفاتورة لن يتم دفعها.",
|
||||||
"bad_debt.dialog.success_message":"تم شطب فاتورة البيع المقدمة بنجاح.",
|
"bad_debt.dialog.success_message":"تم شطب فاتورة البيع المقدمة بنجاح.",
|
||||||
"bad_debt.cancel_alert.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": "<strong>ملاحظة :</strong> يمكن أن تحتوي رسالة قصيرة الواحدة على 160 حرفًا كحد أقصى. <strong>{value}</strong> سيتم استخدام وحدات الرسائل القصيرة لإرسال هذا إشعار عبر الرسائل القصيرة. ",
|
||||||
|
"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": "<strong>ملاحظة :</strong> One SMS unit can contain amaximum of 160 characters. <strong>{value}</strong> 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":"<strong>{value}</strong> إشارة لاسم الشركة الحالي.",
|
||||||
|
"edit_message_text":"تعديل نص رسالة",
|
||||||
|
"enable_notification":"تفعيل الإشعارات",
|
||||||
|
"send_sms":"إرسال رسالة قصيرة",
|
||||||
|
"save_sms_message":"حفظ رسالة قصيرة"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1407,7 +1407,6 @@
|
|||||||
"cash_flow_money_out": "Money Out",
|
"cash_flow_money_out": "Money Out",
|
||||||
"cash_flow_transaction.switch_item": "Transactions {value}",
|
"cash_flow_transaction.switch_item": "Transactions {value}",
|
||||||
"cash_flow_transaction.balance_in_bigcapital": "Balance in Bigcapital",
|
"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": "All customers",
|
||||||
"AR_aging_summary.filter_customers.all_customers.hint": "All customers, include that ones have zero-balance.",
|
"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",
|
"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.bad_debt": "Bad debt",
|
||||||
"bad_debt.dialog.cancel_bad_debt": "Cancel 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.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.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.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.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": "<strong>Note :</strong> One SMS unit can contain a maximum of 160 characters. <strong>{value}</strong> 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": "<strong>Note :</strong> One SMS unit can contain a maximum of 160 characters. <strong>{value}</strong> 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": "<strong>{value}</strong> 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"
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import Accountant from 'containers/Preferences/Accountant/Accountant';
|
|||||||
// import Accounts from 'containers/Preferences/Accounts/Accounts';
|
// import Accounts from 'containers/Preferences/Accounts/Accounts';
|
||||||
import Currencies from 'containers/Preferences/Currencies/Currencies';
|
import Currencies from 'containers/Preferences/Currencies/Currencies';
|
||||||
import Item from 'containers/Preferences/Item';
|
import Item from 'containers/Preferences/Item';
|
||||||
|
import SMSIntegration from '../containers/Preferences/SMSIntegration';
|
||||||
import DefaultRoute from '../containers/Preferences/DefaultRoute';
|
import DefaultRoute from '../containers/Preferences/DefaultRoute';
|
||||||
|
|
||||||
const BASE_URL = '/preferences';
|
const BASE_URL = '/preferences';
|
||||||
@@ -34,6 +35,11 @@ export default [
|
|||||||
component: Item,
|
component: Item,
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${BASE_URL}/sms-message`,
|
||||||
|
component: SMSIntegration,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `${BASE_URL}/`,
|
path: `${BASE_URL}/`,
|
||||||
component: DefaultRoute,
|
component: DefaultRoute,
|
||||||
|
|||||||
@@ -509,4 +509,11 @@ export default {
|
|||||||
],
|
],
|
||||||
viewBox: '0 0 24 24',
|
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",
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
&__inside-content {
|
&__inside-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&--tabable {
|
&--tabable {
|
||||||
margin-left: -25px;
|
margin-left: -25px;
|
||||||
|
|||||||
38
src/style/pages/Preferences/SMSIntegration.scss
Normal file
38
src/style/pages/Preferences/SMSIntegration.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/style/pages/SMSMessage/SMSMessage.scss
Normal file
29
src/style/pages/SMSMessage/SMSMessage.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -341,7 +341,7 @@ export const saveInvoke = (func, ...rest) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const safeInvoke = (func, ...rest) => {
|
export const safeInvoke = (func, ...rest) => {
|
||||||
return func && func(...rest);
|
func && func(...rest);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformToForm = (obj, emptyInitialValues) => {
|
export const transformToForm = (obj, emptyInitialValues) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user