mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 00:00:31 +00:00
feat: Optimize SMS notification module.
This commit is contained in:
@@ -50,10 +50,12 @@ export default function DialogsContainer() {
|
|||||||
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
||||||
<MoneyInDialog dialogName={'money-in'} />
|
<MoneyInDialog dialogName={'money-in'} />
|
||||||
<MoneyOutDialog dialogName={'money-out'} />
|
<MoneyOutDialog dialogName={'money-out'} />
|
||||||
|
|
||||||
<NotifyInvoiceViaSMSDialog dialogName={'notify-invoice-via-sms'} />
|
<NotifyInvoiceViaSMSDialog dialogName={'notify-invoice-via-sms'} />
|
||||||
<NotifyReceiptViaSMSDialog dialogName={'notify-receipt-via-sms'} />
|
<NotifyReceiptViaSMSDialog dialogName={'notify-receipt-via-sms'} />
|
||||||
<NotifyEstimateViaSMSDialog dialogName={'notify-estimate-via-sms'} />
|
<NotifyEstimateViaSMSDialog dialogName={'notify-estimate-via-sms'} />
|
||||||
<NotifyPaymentReceiveViaSMSDialog dialogName={'notify-payment-via-sms'} />
|
<NotifyPaymentReceiveViaSMSDialog dialogName={'notify-payment-via-sms'} />
|
||||||
|
|
||||||
<BadDebtDialog dialogName={'write-off-bad-debt'} />
|
<BadDebtDialog dialogName={'write-off-bad-debt'} />
|
||||||
<SMSMessageDialog dialogName={'sms-message-form'} />
|
<SMSMessageDialog dialogName={'sms-message-form'} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
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: 61px;
|
||||||
|
padding: 12px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 12px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
background: #2fa2e4;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SMSMessagePreviewBase = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 265px;
|
||||||
|
margin: 0 auto;
|
||||||
|
`;
|
||||||
@@ -87,6 +87,7 @@ export * from './IntersectionObserver';
|
|||||||
export * from './Datatable/CellForceWidth';
|
export * from './Datatable/CellForceWidth';
|
||||||
export * from './Button';
|
export * from './Button';
|
||||||
export * from './IntersectionObserver';
|
export * from './IntersectionObserver';
|
||||||
|
export * from './SMSPreview';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ function NotifyEstimateViaSMSForm({
|
|||||||
|
|
||||||
// Handles the form submit.
|
// Handles the form submit.
|
||||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
// Handle request response success.
|
// Handle request response success.
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -31,8 +33,8 @@ function NotifyEstimateViaSMSForm({
|
|||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle request response errors.
|
// Handle request response errors.
|
||||||
const onError = ({
|
const onError = ({
|
||||||
response: {
|
response: {
|
||||||
@@ -40,6 +42,7 @@ function NotifyEstimateViaSMSForm({
|
|||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
transformErrors(errors);
|
transformErrors(errors);
|
||||||
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
createNotifyEstimateBySMSMutate([estimateId, values])
|
createNotifyEstimateBySMSMutate([estimateId, values])
|
||||||
.then(onSuccess)
|
.then(onSuccess)
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ import { transformErrors } from '../../../containers/NotifyViaSMS/utils';
|
|||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const transformFormValuesToRequest = (values) => {
|
||||||
|
return {
|
||||||
|
notification_type: values.notification_key,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify Invoice Via SMS Form.
|
* Notify Invoice Via SMS Form.
|
||||||
*/
|
*/
|
||||||
@@ -23,16 +29,21 @@ function NotifyInvoiceViaSMSForm({
|
|||||||
invoiceId,
|
invoiceId,
|
||||||
invoiceSMSDetail,
|
invoiceSMSDetail,
|
||||||
dialogName,
|
dialogName,
|
||||||
|
notificationType,
|
||||||
|
setNotificationType,
|
||||||
} = useNotifyInvoiceViaSMSContext();
|
} = useNotifyInvoiceViaSMSContext();
|
||||||
|
|
||||||
// Handles the form submit.
|
// Handles the form submit.
|
||||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
// Handle request response success.
|
// Handle request response success.
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('notify_via_sms.dialog.success_message'),
|
message: intl.get('notify_via_sms.dialog.success_message'),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
|
setSubmitting(false);
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,17 +54,37 @@ function NotifyInvoiceViaSMSForm({
|
|||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
transformErrors(errors);
|
transformErrors(errors);
|
||||||
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
createNotifyInvoiceBySMSMutate([invoiceId, values])
|
// Transformes the form values to request.
|
||||||
|
const requestValues = transformFormValuesToRequest(values);
|
||||||
|
|
||||||
|
createNotifyInvoiceBySMSMutate([invoiceId, requestValues])
|
||||||
.then(onSuccess)
|
.then(onSuccess)
|
||||||
.catch(onError);
|
.catch(onError);
|
||||||
};
|
};
|
||||||
|
// Handle the form cancel.
|
||||||
|
const handleFormCancel = React.useCallback(() => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
}, [closeDialog, dialogName]);
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
notification_key: notificationType,
|
||||||
|
...invoiceSMSDetail,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleValuesChange = (values) => {
|
||||||
|
if (values.notification_key !== notificationType) {
|
||||||
|
setNotificationType(values.notification_key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotifyViaSMSForm
|
<NotifyViaSMSForm
|
||||||
NotificationDetail={invoiceSMSDetail}
|
initialValues={initialValues}
|
||||||
NotificationName={dialogName}
|
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
onValuesChange={handleValuesChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,19 @@ import { useCreateNotifyInvoiceBySMS, useInvoiceSMSDetail } from 'hooks/query';
|
|||||||
const NotifyInvoiceViaSMSContext = React.createContext();
|
const NotifyInvoiceViaSMSContext = React.createContext();
|
||||||
|
|
||||||
function NotifyInvoiceViaSMSFormProvider({ invoiceId, dialogName, ...props }) {
|
function NotifyInvoiceViaSMSFormProvider({ invoiceId, dialogName, ...props }) {
|
||||||
const { data: invoiceSMSDetail, isLoading: isInvoiceSMSDetailLoading } =
|
const [notificationType, setNotificationType] = React.useState('details');
|
||||||
useInvoiceSMSDetail(invoiceId, {
|
|
||||||
enabled: !!invoiceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Retrieve the invoice sms notification message details.
|
||||||
|
const { data: invoiceSMSDetail, isLoading: isInvoiceSMSDetailLoading } =
|
||||||
|
useInvoiceSMSDetail(
|
||||||
|
invoiceId,
|
||||||
|
{
|
||||||
|
notification_type: notificationType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!invoiceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
// Create notfiy invoice by sms mutations.
|
// Create notfiy invoice by sms mutations.
|
||||||
const { mutateAsync: createNotifyInvoiceBySMSMutate } =
|
const { mutateAsync: createNotifyInvoiceBySMSMutate } =
|
||||||
useCreateNotifyInvoiceBySMS();
|
useCreateNotifyInvoiceBySMS();
|
||||||
@@ -20,6 +28,9 @@ function NotifyInvoiceViaSMSFormProvider({ invoiceId, dialogName, ...props }) {
|
|||||||
invoiceSMSDetail,
|
invoiceSMSDetail,
|
||||||
dialogName,
|
dialogName,
|
||||||
createNotifyInvoiceBySMSMutate,
|
createNotifyInvoiceBySMSMutate,
|
||||||
|
|
||||||
|
notificationType,
|
||||||
|
setNotificationType,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -32,4 +32,5 @@ function NotifyInvoiceViaSMSDialog({
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogRedux())(NotifyInvoiceViaSMSDialog);
|
export default compose(withDialogRedux())(NotifyInvoiceViaSMSDialog);
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ function SMSMessageForm({
|
|||||||
...omit(values, ['is_notification_enabled', 'sms_message']),
|
...omit(values, ['is_notification_enabled', 'sms_message']),
|
||||||
notification_key: smsNotification.key,
|
notification_key: smsNotification.key,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle request response success.
|
// Handle request response success.
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -53,7 +52,6 @@ function SMSMessageForm({
|
|||||||
});
|
});
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle request response errors.
|
// Handle request response errors.
|
||||||
const onError = ({
|
const onError = ({
|
||||||
response: {
|
response: {
|
||||||
@@ -62,7 +60,7 @@ function SMSMessageForm({
|
|||||||
}) => {
|
}) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
debugger;
|
||||||
editSMSNotificationMutate(form).then(onSuccess).catch(onError);
|
editSMSNotificationMutate(form).then(onSuccess).catch(onError);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,103 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Form } from 'formik';
|
import { Form, useFormikContext } from 'formik';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
import SMSMessageFormFields from './SMSMessageFormFields';
|
import SMSMessageFormFields from './SMSMessageFormFields';
|
||||||
import SMSMessageFormFloatingActions from './SMSMessageFormFloatingActions';
|
import SMSMessageFormFloatingActions from './SMSMessageFormFloatingActions';
|
||||||
|
|
||||||
|
import { SMSMessagePreview } from 'components';
|
||||||
|
import { getSMSUnits } from '../../NotifyViaSMS/utils';
|
||||||
|
|
||||||
|
const messageVariables = [
|
||||||
|
{
|
||||||
|
variable: '{CompanyName}',
|
||||||
|
description: 'References to the current company name.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMS message form content.
|
* SMS message form content.
|
||||||
*/
|
*/
|
||||||
export default function SMSMessageFormContent() {
|
export default function SMSMessageFormContent() {
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<FormContent>
|
||||||
|
<FormFields>
|
||||||
<SMSMessageFormFields />
|
<SMSMessageFormFields />
|
||||||
|
|
||||||
|
<SMSMessageVariables>
|
||||||
|
{messageVariables.map(({ variable, description }) => (
|
||||||
|
<MessageVariable>
|
||||||
|
<strong>{variable}</strong> {description}
|
||||||
|
</MessageVariable>
|
||||||
|
))}
|
||||||
|
</SMSMessageVariables>
|
||||||
|
</FormFields>
|
||||||
|
|
||||||
|
<FormPreview>
|
||||||
|
<SMSMessagePreviewSection />
|
||||||
|
</FormPreview>
|
||||||
|
</FormContent>
|
||||||
|
</div>
|
||||||
<SMSMessageFormFloatingActions />
|
<SMSMessageFormFloatingActions />
|
||||||
</Form>
|
</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>
|
||||||
|
<strong>Note</strong>: Note: One SMS unit can contain a maximum of 160
|
||||||
|
characters. <strong>{messagesUnits}</strong> SMS units will be used to
|
||||||
|
send this SMS notification.
|
||||||
|
</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;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { inputIntent } from 'utils';
|
|||||||
|
|
||||||
export default function SMSMessageFormFields() {
|
export default function SMSMessageFormFields() {
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div>
|
||||||
{/* ----------- Message Text ----------- */}
|
{/* ----------- Message Text ----------- */}
|
||||||
<FastField name={'message_text'}>
|
<FastField name={'message_text'}>
|
||||||
{({ field, meta: { error, touched } }) => (
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
|
import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
|
||||||
@@ -27,7 +29,7 @@ function SMSMessageFormFloatingActions({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<FooterActions className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
|
||||||
<T id={'cancel'} />
|
<T id={'cancel'} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -37,11 +39,15 @@ function SMSMessageFormFloatingActions({
|
|||||||
style={{ minWidth: '75px' }}
|
style={{ minWidth: '75px' }}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{<T id={'save'} />}
|
Save SMS Message
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</FooterActions>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogActions)(SMSMessageFormFloatingActions);
|
export default compose(withDialogActions)(SMSMessageFormFloatingActions);
|
||||||
|
|
||||||
|
const FooterActions = styled.div`
|
||||||
|
justify-content: flex-start;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,28 +1,62 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form, useFormikContext } from 'formik';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
import 'style/pages/NotifyConactViaSMS/NotifyConactViaSMSDialog.scss';
|
import 'style/pages/NotifyConactViaSMS/NotifyConactViaSMSDialog.scss';
|
||||||
|
|
||||||
import { CreateNotifyViaSMSFormSchema } from './NotifyViaSMSForm.schema';
|
import { CreateNotifyViaSMSFormSchema } from './NotifyViaSMSForm.schema';
|
||||||
import NotifyViaSMSFormFields from './NotifyViaSMSFormFields';
|
import NotifyViaSMSFormFields from './NotifyViaSMSFormFields';
|
||||||
import NotifyViaSMSFormFloatingActions from './NotifyViaSMSFormFloatingActions';
|
import NotifyViaSMSFormFloatingActions from './NotifyViaSMSFormFloatingActions';
|
||||||
|
import { FormObserver, SMSMessagePreview } from 'components';
|
||||||
|
|
||||||
import { transformToForm, saveInvoke } from 'utils';
|
import { transformToForm, safeInvoke } from 'utils';
|
||||||
|
import { getSMSUnits } from './utils';
|
||||||
|
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
|
notification_key: '',
|
||||||
customer_name: '',
|
customer_name: '',
|
||||||
customer_phone_number: '',
|
customer_phone_number: '',
|
||||||
sms_message: '',
|
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>
|
||||||
|
<strong>Note</strong>: Note: One SMS unit can contain a maximum of 160
|
||||||
|
characters. <strong>{messagesUnits}</strong> SMS units will be used to
|
||||||
|
send this SMS notification.
|
||||||
|
</SMSPreviewSectionNote>
|
||||||
|
</SMSPreviewSectionRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify Via SMS Form.
|
* Notify Via SMS Form.
|
||||||
*/
|
*/
|
||||||
function NotifyViaSMSForm({ onSubmit, NotificationDetail, NotificationName }) {
|
function NotifyViaSMSForm({
|
||||||
|
initialValues: initialValuesComponent,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
onValuesChange,
|
||||||
|
}) {
|
||||||
// Initial form values
|
// Initial form values
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
...defaultInitialValues,
|
...defaultInitialValues,
|
||||||
...transformToForm(NotificationDetail, defaultInitialValues),
|
...transformToForm(initialValuesComponent, defaultInitialValues),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,11 +66,56 @@ function NotifyViaSMSForm({ onSubmit, NotificationDetail, NotificationName }) {
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<NotifyContent>
|
||||||
|
<NotifyFieldsSection>
|
||||||
<NotifyViaSMSFormFields />
|
<NotifyViaSMSFormFields />
|
||||||
<NotifyViaSMSFormFloatingActions dialogName={NotificationName} />
|
</NotifyFieldsSection>
|
||||||
|
<SMSMessagePreviewSection />
|
||||||
|
</NotifyContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NotifyViaSMSFormFloatingActions onCancel={onCancel} />
|
||||||
|
<NotifyObserveValuesChange onChange={onValuesChange} />
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</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} />;
|
||||||
|
}
|
||||||
|
|
||||||
export default NotifyViaSMSForm;
|
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;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,16 +1,47 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FastField, ErrorMessage } from 'formik';
|
import { FastField, ErrorMessage } from 'formik';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormGroup, InputGroup } from '@blueprintjs/core';
|
||||||
|
|
||||||
import { Classes, FormGroup, TextArea, InputGroup } from '@blueprintjs/core';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ListSelect,
|
||||||
|
FieldRequiredHint,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { inputIntent } from 'utils';
|
import { inputIntent } from 'utils';
|
||||||
import { FieldRequiredHint } from 'components';
|
|
||||||
|
|
||||||
function NotifyViaSMSFormFields() {
|
const notificationTypes = [
|
||||||
|
{ key: 'details', label: 'Invoice details' },
|
||||||
|
{ key: 'reminder', label: 'Invoice reminder' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function NotifyViaSMSFormFields() {
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div>
|
||||||
|
<FastField name={'notification_key'}>
|
||||||
|
{({ form, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={'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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
{/* ----------- Send Notification to ----------- */}
|
{/* ----------- Send Notification to ----------- */}
|
||||||
<FastField name={'customer_name'}>
|
<FastField name={'customer_name'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
@@ -51,29 +82,6 @@ function NotifyViaSMSFormFields() {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/* ----------- Message Text ----------- */}
|
|
||||||
<FastField name={'sms_message'}>
|
|
||||||
{({ field, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'notify_via_sms.dialog.message_text'} />}
|
|
||||||
labelInfo={<FieldRequiredHint />}
|
|
||||||
className={'form-group--sms_message'}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'sms_message'} />}
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
growVertically={true}
|
|
||||||
large={true}
|
|
||||||
disabled={true}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NotifyViaSMSFormFields;
|
|
||||||
|
|||||||
@@ -1,41 +1,47 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import { safeCallback } from 'utils';
|
||||||
import { compose } from 'utils';
|
|
||||||
|
|
||||||
function NotifyViaSMSFormFloatingActions({
|
/**
|
||||||
// #withDialogActions
|
*
|
||||||
closeDialog,
|
*/
|
||||||
dialogName,
|
export default function NotifyViaSMSFormFloatingActions({ onCancel }) {
|
||||||
}) {
|
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
// Handle close button click.
|
// Handle close button click.
|
||||||
const handleCancelBtnClick = () => {
|
const handleCancelBtnClick = (event) => {
|
||||||
closeDialog(dialogName);
|
onCancel && onCancel(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<FooterActions className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
|
|
||||||
<T id={'cancel'} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
style={{ minWidth: '75px' }}
|
style={{ minWidth: '110px' }}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{<T id={'send'} />}
|
Send SMS
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={handleCancelBtnClick}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
</FooterActions>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default compose(withDialogActions)(NotifyViaSMSFormFloatingActions);
|
|
||||||
|
const FooterActions = styled.div`
|
||||||
|
justify-content: flex-start;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -10,3 +10,8 @@ export const transformErrors = (errors) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getSMSUnits = (message, threshold = 140) => {
|
||||||
|
return Math.ceil(message.length / threshold);
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DataTableEditable, DataTable } from 'components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { DataTable } from 'components';
|
||||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
|
|
||||||
import { useSMSIntegrationTableColumns } from './components';
|
import { useSMSIntegrationTableColumns } from './components';
|
||||||
@@ -23,7 +25,7 @@ function SMSMessagesDataTable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<SMSNotificationsTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={notifications}
|
data={notifications}
|
||||||
loading={isSMSNotificationsLoading}
|
loading={isSMSNotificationsLoading}
|
||||||
@@ -38,3 +40,12 @@ function SMSMessagesDataTable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogActions)(SMSMessagesDataTable);
|
export default compose(withDialogActions)(SMSMessagesDataTable);
|
||||||
|
|
||||||
|
const SMSNotificationsTable = styled(DataTable)`
|
||||||
|
.table .tbody .tr .td {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.table .tbody .td {
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { ButtonLink } from 'components';
|
||||||
import { SwitchFieldCell } from 'components/DataTableCells';
|
import { SwitchFieldCell } from 'components/DataTableCells';
|
||||||
import { safeCallback } from 'utils';
|
import { safeInvoke } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification accessor.
|
* Notification accessor.
|
||||||
@@ -9,31 +12,38 @@ import { safeCallback } from 'utils';
|
|||||||
export const NotificationAccessor = (row) => {
|
export const NotificationAccessor = (row) => {
|
||||||
return (
|
return (
|
||||||
<span className="notification">
|
<span className="notification">
|
||||||
<span className={'notification__label'}>{row.notification_label}</span>
|
<NotificationLabel>{row.notification_label}</NotificationLabel>
|
||||||
<span className={'notification__desc'}>
|
<NotificationDescription>
|
||||||
{row.notification_description}
|
{row.notification_description}
|
||||||
</span>
|
</NotificationDescription>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS notification message cell.
|
||||||
|
*/
|
||||||
export const SMSMessageCell = ({
|
export const SMSMessageCell = ({
|
||||||
payload: { onEditSMSMessage },
|
payload: { onEditSMSMessage },
|
||||||
row: { original },
|
row: { original },
|
||||||
}) => (
|
}) => (
|
||||||
<div>
|
<div>
|
||||||
{original.sms_message}
|
<MessageBox>{original.sms_message}</MessageBox>
|
||||||
<span
|
<MessageBoxActions>
|
||||||
className="edit-text"
|
<ButtonLink onClick={() => safeInvoke(onEditSMSMessage, original)}>
|
||||||
onClick={safeCallback(onEditSMSMessage, original)}
|
Edit message
|
||||||
>
|
</ButtonLink>
|
||||||
{'Edit'}
|
</MessageBoxActions>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve SMS notifications messages table columns
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export function useSMSIntegrationTableColumns() {
|
export function useSMSIntegrationTableColumns() {
|
||||||
return React.useMemo(() => [
|
return React.useMemo(
|
||||||
|
() => [
|
||||||
{
|
{
|
||||||
Header: intl.get('sms_message.label_Notification'),
|
Header: intl.get('sms_message.label_Notification'),
|
||||||
accessor: NotificationAccessor,
|
accessor: NotificationAccessor,
|
||||||
@@ -63,5 +73,34 @@ export function useSMSIntegrationTableColumns() {
|
|||||||
disableResizing: true,
|
disableResizing: true,
|
||||||
width: '80',
|
width: '80',
|
||||||
},
|
},
|
||||||
]);
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -254,13 +254,13 @@ export function useCreateNotifyInvoiceBySMS(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useInvoiceSMSDetail(invoiceId, props, requestProps) {
|
export function useInvoiceSMSDetail(invoiceId, query, props) {
|
||||||
return useRequestQuery(
|
return useRequestQuery(
|
||||||
[t.SALE_INVOICE_SMS_DETAIL, invoiceId],
|
[t.SALE_INVOICE_SMS_DETAIL, invoiceId, query],
|
||||||
{
|
{
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `sales/invoices/${invoiceId}/sms-details`,
|
url: `sales/invoices/${invoiceId}/sms-details`,
|
||||||
...requestProps,
|
params: query,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: (res) => res.data.data,
|
select: (res) => res.data.data,
|
||||||
|
|||||||
@@ -1427,23 +1427,23 @@
|
|||||||
"bad_debt.dialog.success_message": "The given sale invoice has been writte-off successfully.",
|
"bad_debt.dialog.success_message": "The given sale invoice has been writte-off successfully.",
|
||||||
"bad_debt.cancel_alert.success_message": "The given sale invoice has been canceled write-off successfully.",
|
"bad_debt.cancel_alert.success_message": "The given sale invoice has been canceled write-off successfully.",
|
||||||
"bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?",
|
"bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?",
|
||||||
"notify_via_sms.dialog.send_notification_to":"Send notification to",
|
"notify_via_sms.dialog.send_notification_to": "Send notification to",
|
||||||
"notify_via_sms.dialog.message_text":"Message Text",
|
"notify_via_sms.dialog.message_text": "Message Text",
|
||||||
"notify_via_sms.dialog.notify_via_sms":"Notify vis SMS",
|
"notify_via_sms.dialog.notify_via_sms": "Notify vis SMS",
|
||||||
"notify_via_sms.dialog.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.success_message": "SMS notification has been sent successfully.",
|
||||||
"notify_invoice_via_sms.dialog.success_message":"The sale invoice sms notification has been sent successfully",
|
"notify_via_sms.dialog.error_message": "Sms notification cannot be sent, customer personal phone number is invalid, please enter a valid one and try again.",
|
||||||
"notify_estimate_via_sms.dialog.success_message":"The sale estimate sms notification has been sent successfully",
|
"notify_invoice_via_sms.dialog.success_message": "The sale invoice sms notification has been sent successfully",
|
||||||
"notify_receipt_via_sms.dialog.success_message":"The sale receipt sms notification has been sent successfully",
|
"notify_estimate_via_sms.dialog.success_message": "The sale estimate sms notification has been sent successfully",
|
||||||
"notify_payment_receive_via_sms.dialog.success_message":"The payment 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.",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"sms_integration.label":"SMS Integration",
|
"sms_integration.label": "SMS Integration",
|
||||||
"sms_integration.label.overview":"Overview",
|
"sms_integration.label.overview": "Overview",
|
||||||
"sms_integration.label.sms_messages":"SMS Messages",
|
"sms_integration.label.sms_messages": "SMS Messages",
|
||||||
"sms_message.label.sms_messages_template":"SMS Notifications ",
|
"sms_message.label.sms_messages_template": "SMS Notifications ",
|
||||||
"sms_message.label_mesage":"Message",
|
"sms_message.label_mesage": "Message",
|
||||||
"sms_message.label_Notification":"Notification",
|
"sms_message.label_Notification": "Notification",
|
||||||
"sms_message.label_auto":"Auto",
|
"sms_message.label_auto": "Auto",
|
||||||
"sms_message":"SMS message",
|
"sms_message": "SMS message",
|
||||||
"sms_message.dialog.success_message":"Sms notification settings has been updated successfully."
|
"sms_message.dialog.success_message": "Sms notification settings has been updated successfully."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -509,4 +509,11 @@ export default {
|
|||||||
],
|
],
|
||||||
viewBox: '0 0 24 24',
|
viewBox: '0 0 24 24',
|
||||||
},
|
},
|
||||||
|
"sms-message-preview": {
|
||||||
|
path: [
|
||||||
|
'M8.341,375.3573H399.3271v-.0015l-390.9861-.07ZM363.2382,0H44.43A44.4508,44.4508,0,0,0,0,44.371V375.284l8.341.0016V44.371A36.0651,36.0651,0,0,1,44.43,8.33H90.7089a4.6454,4.6454,0,0,1,4.6482,4.6423v1.9718a23.8588,23.8588,0,0,0,23.8742,23.843H288.9146a23.8586,23.8586,0,0,0,23.8741-23.843V12.972A4.6456,4.6456,0,0,1,317.4372,8.33h45.801A36.0651,36.0651,0,0,1,399.3271,44.371V375.3558l8.341.0015V44.371A44.4508,44.4508,0,0,0,363.2382,0Z',
|
||||||
|
"M1199.9485,803.1623"
|
||||||
|
],
|
||||||
|
viewBox: "0 0 407.6681 375.3573",
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.dialog--notify-vis-sms {
|
.dialog--notify-vis-sms {
|
||||||
max-width: 350px;
|
width: 800px;
|
||||||
max-height: 450px;
|
|
||||||
|
|
||||||
.bp3-dialog-body {
|
.bp3-dialog-body {
|
||||||
.bp3-form-group {
|
.bp3-form-group {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
&__inside-content {
|
&__inside-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&--tabable {
|
&--tabable {
|
||||||
margin-left: -25px;
|
margin-left: -25px;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.dialog--sms-message {
|
.dialog--sms-message {
|
||||||
max-width: 350px;
|
width: 800px;
|
||||||
|
|
||||||
.bp3-form-group {
|
.bp3-form-group {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ export const saveInvoke = (func, ...rest) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const safeInvoke = (func, ...rest) => {
|
export const safeInvoke = (func, ...rest) => {
|
||||||
return func && func(...rest);
|
func && func(...rest);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformToForm = (obj, emptyInitialValues) => {
|
export const transformToForm = (obj, emptyInitialValues) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user