mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14: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_CURRENCIES: 'preferences-page__inside-content--currencies',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION: 'preferences-page__inside-content--sms-integration',
|
||||
|
||||
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
|
||||
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
|
||||
export const ButtonLink = styled(Button)`
|
||||
line-height: inherit;
|
||||
export const ButtonLink = styled.button`
|
||||
color: #0052cc;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&.bp3-small {
|
||||
min-height: auto;
|
||||
min-width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
&:not([class*='bp3-intent-']) {
|
||||
&,
|
||||
&:hover {
|
||||
color: #0052cc;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:hover,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
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 { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
||||
import NumericInputCell from './NumericInputCell';
|
||||
import CheckBoxFieldCell from './CheckBoxFieldCell'
|
||||
import CheckBoxFieldCell from './CheckBoxFieldCell';
|
||||
import SwitchFieldCell from './SwitchFieldCell';
|
||||
import TextAreaCell from './TextAreaCell';
|
||||
|
||||
export {
|
||||
AccountsListFieldCell,
|
||||
@@ -18,5 +20,7 @@ export {
|
||||
DivFieldCell,
|
||||
EmptyDiv,
|
||||
NumericInputCell,
|
||||
CheckBoxFieldCell
|
||||
CheckBoxFieldCell,
|
||||
SwitchFieldCell,
|
||||
TextAreaCell,
|
||||
};
|
||||
|
||||
@@ -9,16 +9,16 @@ function DialogComponent(props) {
|
||||
const { name, children, closeDialog, onClose } = props;
|
||||
|
||||
const handleClose = (event) => {
|
||||
closeDialog(name)
|
||||
closeDialog(name);
|
||||
onClose && onClose(event);
|
||||
};
|
||||
return (
|
||||
<Dialog {...props} onClose={handleClose}>
|
||||
{ children }
|
||||
{children}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
)(DialogComponent);
|
||||
const DialogRoot = compose(withDialogActions)(DialogComponent);
|
||||
|
||||
export { DialogRoot as Dialog };
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Spinner, Classes } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default function DialogContent(props) {
|
||||
export function DialogContent(props) {
|
||||
const { isLoading, children } = props;
|
||||
|
||||
const loadingContent = (
|
||||
|
||||
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>);
|
||||
}
|
||||
|
||||
export default function DialogSuspense({
|
||||
export function DialogSuspense({
|
||||
children
|
||||
}) {
|
||||
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 MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
|
||||
import BadDebtDialog from '../containers/Dialogs/BadDebtDialog';
|
||||
import NotifyInvoiceViaSMSDialog from '../containers/Dialogs/NotifyInvoiceViaSMSDialog';
|
||||
import NotifyReceiptViaSMSDialog from '../containers/Dialogs/NotifyReceiptViaSMSDialog';
|
||||
import NotifyEstimateViaSMSDialog from '../containers/Dialogs/NotifyEstimateViaSMSDialog';
|
||||
import NotifyPaymentReceiveViaSMSDialog from '../containers/Dialogs/NotifyPaymentReceiveViaSMSDialog'
|
||||
import SMSMessageDialog from '../containers/Dialogs/SMSMessageDialog';
|
||||
|
||||
/**
|
||||
* Dialogs container.
|
||||
@@ -45,7 +50,14 @@ export default function DialogsContainer() {
|
||||
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
||||
<MoneyInDialog dialogName={'money-in'} />
|
||||
<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'} />
|
||||
<SMSMessageDialog dialogName={'sms-message-form'} />
|
||||
</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 LoadingIndicator from './LoadingIndicator';
|
||||
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
|
||||
import Dialog from './Dialog/Dialog';
|
||||
import DialogContent from './Dialog/DialogContent';
|
||||
import DialogSuspense from './Dialog/DialogSuspense';
|
||||
import InputPrependButton from './Forms/InputPrependButton';
|
||||
import CategoriesSelectList from './CategoriesSelectList';
|
||||
import Row from './Grid/Row';
|
||||
@@ -61,8 +58,9 @@ import Card from './Card';
|
||||
import AvaterCell from './AvaterCell';
|
||||
|
||||
import { ItemsMultiSelect } from './Items';
|
||||
import MoreVertMenutItems from './MoreVertMenutItems';
|
||||
import MoreMenuItems from './MoreMenutItems';
|
||||
|
||||
export * from './Dialog';
|
||||
export * from './Menu';
|
||||
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
||||
export * from './AdvancedFilter/AdvancedFilterPopover';
|
||||
@@ -83,10 +81,11 @@ export * from './MultiSelectTaggable';
|
||||
export * from './Utils/FormatNumber';
|
||||
export * from './Utils/FormatDate';
|
||||
export * from './BankAccounts';
|
||||
export * from './IntersectionObserver'
|
||||
export * from './IntersectionObserver';
|
||||
export * from './Datatable/CellForceWidth';
|
||||
export * from './Button';
|
||||
export * from './IntersectionObserver';
|
||||
export * from './SMSPreview';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
@@ -120,9 +119,6 @@ export {
|
||||
LoadingIndicator,
|
||||
DashboardActionViewsList,
|
||||
AppToaster,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogSuspense,
|
||||
InputPrependButton,
|
||||
CategoriesSelectList,
|
||||
Col,
|
||||
@@ -158,5 +154,5 @@ export {
|
||||
ItemsMultiSelect,
|
||||
Card,
|
||||
AvaterCell,
|
||||
MoreVertMenutItems,
|
||||
MoreMenuItems,
|
||||
};
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import React from 'react'
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
export default [
|
||||
{
|
||||
text: <T id={'general'}/>,
|
||||
text: <T id={'general'} />,
|
||||
disabled: false,
|
||||
href: '/preferences/general',
|
||||
},
|
||||
{
|
||||
text: <T id={'users'}/>,
|
||||
text: <T id={'users'} />,
|
||||
href: '/preferences/users',
|
||||
},
|
||||
{
|
||||
text: <T id={'currencies'}/>,
|
||||
|
||||
text: <T id={'currencies'} />,
|
||||
|
||||
href: '/preferences/currencies',
|
||||
},
|
||||
{
|
||||
text: <T id={'accountant'}/>,
|
||||
text: <T id={'accountant'} />,
|
||||
disabled: false,
|
||||
href: '/preferences/accountant',
|
||||
},
|
||||
{
|
||||
text: <T id={'items'}/>,
|
||||
text: <T id={'items'} />,
|
||||
disabled: false,
|
||||
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 withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
import { Icon, FormattedMessage as T, MoreMenuItems } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
@@ -51,6 +51,10 @@ function EstimateDetailActionsBar({
|
||||
const handlePrintEstimate = () => {
|
||||
openDialog('estimate-pdf-preview', { estimateId });
|
||||
};
|
||||
// Handle notify via SMS.
|
||||
const handleNotifyViaSMS = () => {
|
||||
openDialog('notify-estimate-via-sms', { estimateId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -75,6 +79,12 @@ function EstimateDetailActionsBar({
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteEstimate}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<MoreMenuItems
|
||||
payload={{
|
||||
onNotifyViaSMS: handleNotifyViaSMS,
|
||||
}}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
|
||||
@@ -6,28 +6,17 @@ import {
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Intent,
|
||||
MenuItem,
|
||||
Menu,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
import { moreVertOptions } from '../../../common/moreVertOptions';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import {
|
||||
If,
|
||||
Icon,
|
||||
FormattedMessage as T,
|
||||
// MoreVertMenutItems,
|
||||
} from 'components';
|
||||
import { If, Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
@@ -76,6 +65,10 @@ function InvoiceDetailActionsBar({
|
||||
const handleBadDebtInvoice = () => {
|
||||
openDialog('write-off-bad-debt', { invoiceId });
|
||||
};
|
||||
// Handle notify via SMS.
|
||||
const handleNotifyViaSMS = () => {
|
||||
openDialog('notify-invoice-via-sms', { invoiceId });
|
||||
};
|
||||
|
||||
// Handle cancele write-off invoice.
|
||||
const handleCancelBadDebtInvoice = () => {
|
||||
@@ -116,9 +109,11 @@ function InvoiceDetailActionsBar({
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<BadDebtMenuItem
|
||||
invoice={invoice}
|
||||
onAlert={handleCancelBadDebtInvoice}
|
||||
onDialog={handleBadDebtInvoice}
|
||||
payload={{
|
||||
onBadDebt: handleBadDebtInvoice,
|
||||
onCancelBadDebt: handleCancelBadDebtInvoice,
|
||||
onNotifyViaSMS: handleNotifyViaSMS,
|
||||
}}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '@blueprintjs/core';
|
||||
import { Icon, FormattedMessage as T, Choose } from 'components';
|
||||
import { FormatNumberCell } from '../../../components';
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Retrieve invoice readonly details table columns.
|
||||
@@ -58,7 +59,11 @@ export const useInvoiceReadonlyEntriesColumns = () =>
|
||||
[],
|
||||
);
|
||||
|
||||
export const BadDebtMenuItem = ({ invoice, onDialog, onAlert }) => {
|
||||
export const BadDebtMenuItem = ({
|
||||
payload: { onCancelBadDebt, onBadDebt, onNotifyViaSMS },
|
||||
}) => {
|
||||
const { invoice } = useInvoiceDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<Popover
|
||||
minimal={true}
|
||||
@@ -73,16 +78,20 @@ export const BadDebtMenuItem = ({ invoice, onDialog, onAlert }) => {
|
||||
<Choose.When condition={!invoice.is_writtenoff}>
|
||||
<MenuItem
|
||||
text={<T id={'bad_debt.dialog.bad_debt'} />}
|
||||
onClick={onDialog}
|
||||
onClick={onBadDebt}
|
||||
/>
|
||||
</Choose.When>
|
||||
<Choose.When condition={invoice.is_writtenoff}>
|
||||
<MenuItem
|
||||
onClick={onAlert}
|
||||
onClick={onCancelBadDebt}
|
||||
text={<T id={'bad_debt.dialog.cancel_bad_debt'} />}
|
||||
/>
|
||||
</Choose.When>
|
||||
</Choose>
|
||||
<MenuItem
|
||||
onClick={onNotifyViaSMS}
|
||||
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -16,7 +16,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
import { Icon, FormattedMessage as T, MoreMenuItems } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
@@ -29,6 +29,9 @@ function PaymentReceiveActionsBar({
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -46,6 +49,11 @@ function PaymentReceiveActionsBar({
|
||||
openAlert('payment-receive-delete', { paymentReceiveId });
|
||||
};
|
||||
|
||||
// Handle notify via SMS.
|
||||
const handleNotifyViaSMS = () => {
|
||||
openDialog('notify-payment-via-sms', { paymentReceiveId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -63,6 +71,12 @@ function PaymentReceiveActionsBar({
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeletePaymentReceive}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<MoreMenuItems
|
||||
payload={{
|
||||
onNotifyViaSMS: handleNotifyViaSMS,
|
||||
}}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
import { Icon, FormattedMessage as T, MoreMenuItems } from 'components';
|
||||
import { useReceiptDetailDrawerContext } from './ReceiptDetailDrawerProvider';
|
||||
|
||||
import { safeCallback, compose } from 'utils';
|
||||
@@ -46,6 +46,11 @@ function ReceiptDetailActionBar({
|
||||
const onPrintReceipt = () => {
|
||||
openDialog('receipt-pdf-preview', { receiptId });
|
||||
};
|
||||
|
||||
// Handle notify via SMS.
|
||||
const handleNotifyViaSMS = () => {
|
||||
openDialog('notify-receipt-via-sms', { receiptId });
|
||||
};
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -69,6 +74,12 @@ function ReceiptDetailActionBar({
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDeleteReceipt)}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<MoreMenuItems
|
||||
payload={{
|
||||
onNotifyViaSMS: handleNotifyViaSMS,
|
||||
}}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</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) {
|
||||
return !user.is_invite_accepted ? (
|
||||
<Tag minimal={true}>
|
||||
<Tag minimal={true} >
|
||||
<T id={'inviting'} />
|
||||
</Tag>
|
||||
) : user.active ? (
|
||||
|
||||
@@ -20,6 +20,12 @@ const commonInvalidateQueries = (queryClient) => {
|
||||
|
||||
// Invalidate the financial reports.
|
||||
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
|
||||
|
||||
// Invalidate SMS details.
|
||||
queryClient.invalidateQueries(t.SALE_ESTIMATE_SMS_DETAIL);
|
||||
queryClient.invalidateQueries(t.SALE_INVOICE_SMS_DETAIL);
|
||||
queryClient.invalidateQueries(t.SALE_RECEIPT_SMS_DETAIL);
|
||||
queryClient.invalidateQueries(t.PAYMENT_RECEIVE_SMS_DETAIL);
|
||||
};
|
||||
|
||||
// Customers response selector.
|
||||
|
||||
@@ -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 apiRequest = useApiRequest();
|
||||
|
||||
return useMutation((id) => apiRequest.post(`sales/invoices/${id}/writeoff/cancel`), {
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate
|
||||
queryClient.invalidateQueries([t.CANCEL_BAD_DEBT, id]);
|
||||
return useMutation(
|
||||
(id) => apiRequest.post(`sales/invoices/${id}/writeoff/cancel`),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate
|
||||
queryClient.invalidateQueries([t.CANCEL_BAD_DEBT, id]);
|
||||
|
||||
// Common invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
// Common invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
...props,
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
export function useCreateNotifyInvoiceBySMS(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([id, values]) =>
|
||||
apiRequest.post(`sales/invoices/${id}/notify-by-sms`, values),
|
||||
{
|
||||
onSuccess: (res, [id, values]) => {
|
||||
// Invalidate
|
||||
queryClient.invalidateQueries([t.NOTIFY_SALE_INVOICE_BY_SMS, id]);
|
||||
|
||||
// Common invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useInvoiceSMSDetail(invoiceId, query, props) {
|
||||
return useRequestQuery(
|
||||
[t.SALE_INVOICE_SMS_DETAIL, invoiceId, query],
|
||||
{
|
||||
method: 'get',
|
||||
url: `sales/invoices/${invoiceId}/sms-details`,
|
||||
params: query,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const commonInvalidateQueries = (queryClient) => {
|
||||
// Invalidate the cashflow transactions.
|
||||
queryClient.invalidateQueries(t.CASH_FLOW_TRANSACTIONS);
|
||||
queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY);
|
||||
|
||||
|
||||
// Invalidate the settings.
|
||||
queryClient.invalidateQueries([t.SETTING, t.SETTING_RECEIPTS]);
|
||||
};
|
||||
@@ -163,3 +163,37 @@ export function useRefreshReceipts() {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreateNotifyReceiptBySMS(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
return useMutation(
|
||||
([id, values]) =>
|
||||
apiRequest.post(`sales/receipts/${id}/notify-by-sms`, values),
|
||||
{
|
||||
onSuccess: (res, [id, values]) => {
|
||||
queryClient.invalidateQueries([t.NOTIFY_SALE_RECEIPT_BY_SMS, id]);
|
||||
|
||||
// Invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useReceiptSMSDetail(receiptId, props, requestProps) {
|
||||
return useRequestQuery(
|
||||
[t.SALE_RECEIPT_SMS_DETAIL, receiptId],
|
||||
{
|
||||
method: 'get',
|
||||
url: `sales/receipts/${receiptId}/sms-details`,
|
||||
...requestProps,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { useRequestQuery } from '../useQueryRequest';
|
||||
import useApiRequest from '../useRequest';
|
||||
@@ -123,3 +122,61 @@ export function useSettingCashFlow(props) {
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve SMS Notifications settings.
|
||||
*/
|
||||
export function useSettingSMSNotifications(props) {
|
||||
return useRequestQuery(
|
||||
[t.SETTING_SMS_NOTIFICATIONS],
|
||||
{ method: 'get', url: `settings/sms-notifications` },
|
||||
{
|
||||
select: (res) => res.data.notifications,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Specific SMS Notification settings.
|
||||
*/
|
||||
export function useSettingSMSNotification(key, props) {
|
||||
return useRequestQuery(
|
||||
[t.SETTING_SMS_NOTIFICATIONS, key],
|
||||
{
|
||||
method: 'get',
|
||||
url: `settings/sms-notification/${key}`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.notification,
|
||||
defaultData: {
|
||||
smsNotification: [],
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Edit SMS Notification settings.
|
||||
*/
|
||||
export function useSettingEditSMSNotification(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
(values) => apiRequest.post(`settings/sms-notification`, values),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([t.SETTING_SMS_NOTIFICATIONS]);
|
||||
|
||||
queryClient.invalidateQueries(t.SALE_INVOICE_SMS_DETAIL);
|
||||
queryClient.invalidateQueries(t.SALE_RECEIPT_SMS_DETAIL);
|
||||
queryClient.invalidateQueries(t.PAYMENT_RECEIVE_SMS_DETAIL);
|
||||
queryClient.invalidateQueries(t.SALE_ESTIMATE_SMS_DETAIL);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,11 +51,15 @@ const ITEMS = {
|
||||
const SALE_ESTIMATES = {
|
||||
SALE_ESTIMATES: 'SALE_ESTIMATES',
|
||||
SALE_ESTIMATE: 'SALE_ESTIMATE',
|
||||
SALE_ESTIMATE_SMS_DETAIL: 'SALE_ESTIMATE_SMS_DETAIL',
|
||||
NOTIFY_SALE_ESTIMATE_BY_SMS: 'NOTIFY_SALE_ESTIMATE_BY_SMS',
|
||||
};
|
||||
|
||||
const SALE_RECEIPTS = {
|
||||
SALE_RECEIPTS: 'SALE_RECEIPTS',
|
||||
SALE_RECEIPT: 'SALE_RECEIPT',
|
||||
SALE_RECEIPT_SMS_DETAIL: 'SALE_RECEIPT_SMS_DETAIL',
|
||||
NOTIFY_SALE_RECEIPT_BY_SMS: 'NOTIFY_SALE_RECEIPT_BY_SMS',
|
||||
};
|
||||
|
||||
const INVENTORY_ADJUSTMENTS = {
|
||||
@@ -79,12 +83,16 @@ const PAYMENT_RECEIVES = {
|
||||
PAYMENT_RECEIVE: 'PAYMENT_RECEIVE',
|
||||
PAYMENT_RECEIVE_NEW_ENTRIES: 'PAYMENT_RECEIVE_NEW_ENTRIES',
|
||||
PAYMENT_RECEIVE_EDIT_PAGE: 'PAYMENT_RECEIVE_EDIT_PAGE',
|
||||
PAYMENT_RECEIVE_SMS_DETAIL: 'PAYMENT_RECEIVE_SMS_DETAIL',
|
||||
NOTIFY_PAYMENT_RECEIVE_BY_SMS: 'NOTIFY_PAYMENT_RECEIVE_BY_SMS',
|
||||
};
|
||||
|
||||
const SALE_INVOICES = {
|
||||
SALE_INVOICES: 'SALE_INVOICES',
|
||||
SALE_INVOICE: 'SALE_INVOICE',
|
||||
SALE_INVOICES_DUE: 'SALE_INVOICES_DUE',
|
||||
SALE_INVOICE_SMS_DETAIL: 'SALE_INVOICE_SMS_DETAIL',
|
||||
NOTIFY_SALE_INVOICE_BY_SMS: 'NOTIFY_SALE_INVOICE_BY_SMS',
|
||||
BAD_DEBT: 'BAD_DEBT',
|
||||
CANCEL_BAD_DEBT: 'CANCEL_BAD_DEBT',
|
||||
};
|
||||
@@ -103,6 +111,9 @@ const SETTING = {
|
||||
SETTING_MANUAL_JOURNALS: 'SETTING_MANUAL_JOURNALS',
|
||||
SETTING_ITEMS: 'SETTING_ITEMS',
|
||||
SETTING_CASHFLOW: 'SETTING_CASHFLOW',
|
||||
SETTING_SMS_NOTIFICATION: 'SETTING_SMS_NOTIFICATION',
|
||||
SETTING_SMS_NOTIFICATIONS: 'SETTING_SMS_NOTIFICATIONS',
|
||||
SETTING_EDIT_SMS_NOTIFICATION: 'SETTING_EDIT_SMS_NOTIFICATION',
|
||||
};
|
||||
|
||||
const ORGANIZATIONS = {
|
||||
|
||||
@@ -1439,5 +1439,36 @@
|
||||
"bad_debt.dialog.header_note": "يمكن للبائع تحميل مبلغ الفاتورة على حساب مصروفات الديون المعدومة عندما يكون من المؤكد أن الفاتورة لن يتم دفعها.",
|
||||
"bad_debt.dialog.success_message":"تم شطب فاتورة البيع المقدمة بنجاح.",
|
||||
"bad_debt.cancel_alert.success_message":"تم إلغاء شطب فاتورة البيع المقدمة بنجاح.",
|
||||
"bad_debt.cancel_alert.message": "هل أنت متأكد أنك تريد شطب هذه الفاتورة؟ "
|
||||
"bad_debt.cancel_alert.message": "هل أنت متأكد أنك تريد شطب هذه الفاتورة؟ ",
|
||||
"notify_via_sms.dialog.send_notification_to":"إرسال إشعار إلى ",
|
||||
"notify_via_sms.dialog.message_text":"نص رسالة ",
|
||||
"notify_via_sms.dialog.notification_type": "نوع إشعار",
|
||||
"notify_via_sms.dialog.notify_via_sms":"إشعار عبر رسائل قصيرة",
|
||||
"notiify_via_sms.dialog.sms_note": "<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_transaction.switch_item": "Transactions {value}",
|
||||
"cash_flow_transaction.balance_in_bigcapital": "Balance in Bigcapital",
|
||||
|
||||
"AR_aging_summary.filter_customers.all_customers": "All customers",
|
||||
"AR_aging_summary.filter_customers.all_customers.hint": "All customers, include that ones have zero-balance.",
|
||||
"AR_aging_summary.filter_customers.without_zero_balance": "Customers without zero balance",
|
||||
@@ -1425,8 +1424,37 @@
|
||||
"bad_debt.dialog.bad_debt": "Bad debt",
|
||||
"bad_debt.dialog.cancel_bad_debt": "Cancel bad debt",
|
||||
"bad_debt.dialog.header_note": "The seller can charge the amount of an invoice to the bad debt expense account when it is certain that the invoice will not be paid.",
|
||||
"bad_debt.dialog.success_message":"The given sale invoice has been writte-off successfully.",
|
||||
"bad_debt.cancel_alert.success_message":"The given sale invoice has been canceled write-off successfully.",
|
||||
"bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?"
|
||||
|
||||
}
|
||||
"bad_debt.dialog.success_message": "The given sale invoice has been writte-off successfully.",
|
||||
"bad_debt.cancel_alert.success_message": "The given sale invoice has been canceled write-off successfully.",
|
||||
"bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?",
|
||||
"notify_via_sms.dialog.send_notification_to": "Send notification to",
|
||||
"notify_via_sms.dialog.message_text": "Message Text",
|
||||
"notify_via_sms.dialog.notification_type": "Notification type",
|
||||
"notify_via_sms.dialog.notify_via_sms": "Notify vis SMS",
|
||||
"notiify_via_sms.dialog.sms_note": "<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 Currencies from 'containers/Preferences/Currencies/Currencies';
|
||||
import Item from 'containers/Preferences/Item';
|
||||
import SMSIntegration from '../containers/Preferences/SMSIntegration';
|
||||
import DefaultRoute from '../containers/Preferences/DefaultRoute';
|
||||
|
||||
const BASE_URL = '/preferences';
|
||||
@@ -34,6 +35,11 @@ export default [
|
||||
component: Item,
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: `${BASE_URL}/sms-message`,
|
||||
component: SMSIntegration,
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: `${BASE_URL}/`,
|
||||
component: DefaultRoute,
|
||||
|
||||
@@ -509,4 +509,11 @@ export default {
|
||||
],
|
||||
viewBox: '0 0 24 24',
|
||||
},
|
||||
"sms-message-preview": {
|
||||
path: [
|
||||
'M8.341,375.3573H399.3271v-.0015l-390.9861-.07ZM363.2382,0H44.43A44.4508,44.4508,0,0,0,0,44.371V375.284l8.341.0016V44.371A36.0651,36.0651,0,0,1,44.43,8.33H90.7089a4.6454,4.6454,0,0,1,4.6482,4.6423v1.9718a23.8588,23.8588,0,0,0,23.8742,23.843H288.9146a23.8586,23.8586,0,0,0,23.8741-23.843V12.972A4.6456,4.6456,0,0,1,317.4372,8.33h45.801A36.0651,36.0651,0,0,1,399.3271,44.371V375.3558l8.341.0015V44.371A44.4508,44.4508,0,0,0,363.2382,0Z',
|
||||
"M1199.9485,803.1623"
|
||||
],
|
||||
viewBox: "0 0 407.6681 375.3573",
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,8 +17,7 @@
|
||||
&__inside-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
|
||||
&--tabable {
|
||||
margin-left: -25px;
|
||||
margin-right: -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) => {
|
||||
return func && func(...rest);
|
||||
func && func(...rest);
|
||||
};
|
||||
|
||||
export const transformToForm = (obj, emptyInitialValues) => {
|
||||
|
||||
Reference in New Issue
Block a user