mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat: add SMS message template.
This commit is contained in:
43
src/components/DataTableCells/SwitchFieldCell.js
Normal file
43
src/components/DataTableCells/SwitchFieldCell.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import { Classes, Switch, FormGroup, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
const SwitchEditableCell = ({
|
||||||
|
row: { index, original },
|
||||||
|
column: { id, switchProps },
|
||||||
|
cell: { value: initialValue },
|
||||||
|
payload,
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = React.useState(initialValue);
|
||||||
|
|
||||||
|
const onChange = (e) => {
|
||||||
|
const newValue = e.target.checked;
|
||||||
|
|
||||||
|
setValue(newValue);
|
||||||
|
payload.updateData(index, id, newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setValue(initialValue);
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
|
const error = payload.errors?.[index]?.[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
intent={error ? Intent.DANGER : null}
|
||||||
|
className={classNames(Classes.FILL)}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
checked={initialValue}
|
||||||
|
minimal={true}
|
||||||
|
className="ml2"
|
||||||
|
{...switchProps}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default SwitchEditableCell;
|
||||||
42
src/components/DataTableCells/TextAreaCell.js
Normal file
42
src/components/DataTableCells/TextAreaCell.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Classes, TextArea, FormGroup, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
const TextAreaEditableCell = ({
|
||||||
|
row: { index },
|
||||||
|
column: { id },
|
||||||
|
cell: { value: initialValue },
|
||||||
|
payload,
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = useState(initialValue);
|
||||||
|
|
||||||
|
const onChange = (e) => {
|
||||||
|
setValue(e.target.value);
|
||||||
|
};
|
||||||
|
const onBlur = () => {
|
||||||
|
payload.updateData(index, id, value);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(initialValue);
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
|
const error = payload.errors?.[index]?.[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
intent={error ? Intent.DANGER : null}
|
||||||
|
className={classNames(Classes.FILL)}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
fill={true}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextAreaEditableCell;
|
||||||
@@ -6,7 +6,9 @@ import ItemsListCell from './ItemsListCell';
|
|||||||
import PercentFieldCell from './PercentFieldCell';
|
import PercentFieldCell from './PercentFieldCell';
|
||||||
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
||||||
import NumericInputCell from './NumericInputCell';
|
import NumericInputCell from './NumericInputCell';
|
||||||
import CheckBoxFieldCell from './CheckBoxFieldCell'
|
import CheckBoxFieldCell from './CheckBoxFieldCell';
|
||||||
|
import SwitchFieldCell from './SwitchFieldCell';
|
||||||
|
import TextAreaCell from './TextAreaCell';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AccountsListFieldCell,
|
AccountsListFieldCell,
|
||||||
@@ -18,5 +20,7 @@ export {
|
|||||||
DivFieldCell,
|
DivFieldCell,
|
||||||
EmptyDiv,
|
EmptyDiv,
|
||||||
NumericInputCell,
|
NumericInputCell,
|
||||||
CheckBoxFieldCell
|
CheckBoxFieldCell,
|
||||||
|
SwitchFieldCell,
|
||||||
|
TextAreaCell,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
text: <T id={'general'}/>,
|
text: <T id={'general'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/general',
|
href: '/preferences/general',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'users'}/>,
|
text: <T id={'users'} />,
|
||||||
href: '/preferences/users',
|
href: '/preferences/users',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'currencies'}/>,
|
text: <T id={'currencies'} />,
|
||||||
|
|
||||||
href: '/preferences/currencies',
|
href: '/preferences/currencies',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'accountant'}/>,
|
text: <T id={'accountant'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/accountant',
|
href: '/preferences/accountant',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'items'}/>,
|
text: <T id={'items'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/items',
|
href: '/preferences/items',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'sms_message'} />,
|
||||||
|
disabled: false,
|
||||||
|
href: '/preferences/sms-message',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
function SMSMessageTemplateFloatingAction() {
|
||||||
|
const history = useHistory();
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
const handleCloseClick = () => {
|
||||||
|
history.go(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'card__footer'}>
|
||||||
|
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
|
||||||
|
<T id={'save'} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCloseClick} disabled={isSubmitting}>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SMSMessageTemplateFloatingAction;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Formik, Form } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import { CreateSMSMessageTemplateSchema } from './SMSMessageTemplateForm.schema';
|
||||||
|
|
||||||
|
import SMSMessageTemplateFormContent from './SMSMessageTemplateFormContent';
|
||||||
|
|
||||||
|
export const defaultInitialValues = {
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
notification: '',
|
||||||
|
service: '',
|
||||||
|
message: '',
|
||||||
|
auto: true,
|
||||||
|
switch: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SMSMessageTemplateForm({}) {
|
||||||
|
// Form initial values.
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles form submit.
|
||||||
|
const handleSubmit = (values, { setSubmitting, setErrors, resetForm }) => {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_STRIP_STYLE)}
|
||||||
|
>
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateSMSMessageTemplateSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
component={SMSMessageTemplateFormContent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
||||||
|
import { isBlank } from 'utils';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
|
||||||
|
entries: Yup.array().of(
|
||||||
|
Yup.object().shape({
|
||||||
|
notification: Yup.string().nullable(),
|
||||||
|
service: Yup.number().nullable(),
|
||||||
|
message: Yup.string().max(DATATYPES_LENGTH.TEXT).nullable(),
|
||||||
|
auto:Yup.boolean(),
|
||||||
|
switch:Yup.boolean(),
|
||||||
|
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
export const CreateSMSMessageTemplateSchema = Schema;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField } from 'formik';
|
||||||
|
|
||||||
|
import SMSMessageTemplatesEntriesTable from './SMSMessageTemplatesEntriesTable';
|
||||||
|
|
||||||
|
export default function SMSMessageTemplateFormBody() {
|
||||||
|
return (
|
||||||
|
<FastField name={'entries'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<SMSMessageTemplatesEntriesTable
|
||||||
|
entries={value}
|
||||||
|
onUpdateData={(newEntries) => {
|
||||||
|
setFieldValue('entries', newEntries);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import SMSMessageTemplateFormBody from './SMSMessageTemplateFormBody';
|
||||||
|
import SMSMessageTemplateFloatingAction from './SMSMessageTemplateFloatingAction';
|
||||||
|
|
||||||
|
export default function SMSMessageTemplateFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<SMSMessageTemplateFormBody />
|
||||||
|
<SMSMessageTemplateFloatingAction />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useSettings, useSaveSettings } from 'hooks/query';
|
||||||
|
|
||||||
|
import PreferencesPageLoader from '../PreferencesPageLoader';
|
||||||
|
|
||||||
|
const SMSMessageTemplateContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS message template provider.
|
||||||
|
*/
|
||||||
|
function SMSMessageTemplateProvider({ ...props }) {
|
||||||
|
|
||||||
|
//Fetches Organization Settings.
|
||||||
|
const { isLoading: isSettingsLoading } = useSettings();
|
||||||
|
|
||||||
|
// Provider state.
|
||||||
|
const provider = {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
|
||||||
|
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={classNames(CLASSES.CARD)}>
|
||||||
|
{isSettingsLoading ? (
|
||||||
|
<PreferencesPageLoader />
|
||||||
|
) : (
|
||||||
|
<SMSMessageTemplateContext.Provider value={provider} {...props} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSMSMessageTemplateContext = () =>
|
||||||
|
React.useContext(SMSMessageTemplateContext);
|
||||||
|
|
||||||
|
export { SMSMessageTemplateProvider, useSMSMessageTemplateContext };
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import {
|
||||||
|
InputGroupCell,
|
||||||
|
TextAreaCell,
|
||||||
|
SwitchFieldCell,
|
||||||
|
} from 'components/DataTableCells';
|
||||||
|
import { DataTableEditable } from 'components';
|
||||||
|
import { compose, updateTableCell } from 'utils';
|
||||||
|
|
||||||
|
export default function SMSMessageTemplatesEntriesTable({
|
||||||
|
onUpdateData,
|
||||||
|
entries,
|
||||||
|
}) {
|
||||||
|
const columns = React.useMemo(() => [
|
||||||
|
{
|
||||||
|
Header: intl.get('sms_message_template.label_Notification'),
|
||||||
|
accessor: 'notification',
|
||||||
|
Cell: InputGroupCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: '150',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: intl.get('service'),
|
||||||
|
accessor: 'service',
|
||||||
|
Cell: InputGroupCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: '150',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: intl.get('sms_message_template.label_mesage'),
|
||||||
|
accessor: 'message',
|
||||||
|
// Cell: TextAreaCell,
|
||||||
|
Cell: InputGroupCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: '150',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: intl.get('sms_message_template.label_auto'),
|
||||||
|
accessor: 'auto',
|
||||||
|
Cell: SwitchFieldCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
disableResizing: true,
|
||||||
|
width: '120',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleUpdateData = React.useCallback(
|
||||||
|
(rowIndex, columnId, value) => {
|
||||||
|
const newRows = compose(updateTableCell(rowIndex, columnId, value))(
|
||||||
|
entries,
|
||||||
|
);
|
||||||
|
onUpdateData(newRows);
|
||||||
|
},
|
||||||
|
[onUpdateData, entries],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTableEditable
|
||||||
|
columns={columns}
|
||||||
|
data={entries}
|
||||||
|
payload={{
|
||||||
|
errors: [],
|
||||||
|
updateData: handleUpdateData,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/containers/Preferences/SMSMessageTemplates/index.js
Normal file
14
src/containers/Preferences/SMSMessageTemplates/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SMSMessageTemplateProvider } from './SMSMessageTemplateProvider';
|
||||||
|
import SMSMessageTemplateForm from './SMSMessageTemplateForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS message template.
|
||||||
|
*/
|
||||||
|
export default function SMSMessageTemplates() {
|
||||||
|
return (
|
||||||
|
<SMSMessageTemplateProvider>
|
||||||
|
<SMSMessageTemplateForm />
|
||||||
|
</SMSMessageTemplateProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1407,7 +1407,6 @@
|
|||||||
"cash_flow_money_out": "Money Out",
|
"cash_flow_money_out": "Money Out",
|
||||||
"cash_flow_transaction.switch_item": "Transactions {value}",
|
"cash_flow_transaction.switch_item": "Transactions {value}",
|
||||||
"cash_flow_transaction.balance_in_bigcapital": "Balance in Bigcapital",
|
"cash_flow_transaction.balance_in_bigcapital": "Balance in Bigcapital",
|
||||||
|
|
||||||
"AR_aging_summary.filter_customers.all_customers": "All customers",
|
"AR_aging_summary.filter_customers.all_customers": "All customers",
|
||||||
"AR_aging_summary.filter_customers.all_customers.hint": "All customers, include that ones have zero-balance.",
|
"AR_aging_summary.filter_customers.all_customers.hint": "All customers, include that ones have zero-balance.",
|
||||||
"AR_aging_summary.filter_customers.without_zero_balance": "Customers without zero balance",
|
"AR_aging_summary.filter_customers.without_zero_balance": "Customers without zero balance",
|
||||||
@@ -1425,8 +1424,15 @@
|
|||||||
"bad_debt.dialog.bad_debt": "Bad debt",
|
"bad_debt.dialog.bad_debt": "Bad debt",
|
||||||
"bad_debt.dialog.cancel_bad_debt": "Cancel bad debt",
|
"bad_debt.dialog.cancel_bad_debt": "Cancel bad debt",
|
||||||
"bad_debt.dialog.header_note": "The seller can charge the amount of an invoice to the bad debt expense account when it is certain that the invoice will not be paid.",
|
"bad_debt.dialog.header_note": "The seller can charge the amount of an invoice to the bad debt expense account when it is certain that the invoice will not be paid.",
|
||||||
"bad_debt.dialog.success_message":"The given sale invoice has been writte-off successfully.",
|
"bad_debt.dialog.success_message": "The given sale invoice has been writte-off successfully.",
|
||||||
"bad_debt.cancel_alert.success_message":"The given sale invoice has been canceled write-off successfully.",
|
"bad_debt.cancel_alert.success_message": "The given sale invoice has been canceled write-off successfully.",
|
||||||
"bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?"
|
"bad_debt.cancel_alert.message": "Are you sure you want to write off this invoice?",
|
||||||
|
"notify_via_sms.dialog.send_notification_to":"Send notification to",
|
||||||
|
"notify_via_sms.dialog.message_text":"Message Text",
|
||||||
|
"notify_via_sms.dialog.notify_via_sms":"Notify vis SMS",
|
||||||
|
"send": "Send",
|
||||||
|
"sms_message_template.label_mesage":"Message",
|
||||||
|
"sms_message_template.label_Notification":"Notification",
|
||||||
|
"sms_message_template.label_auto":"Auto",
|
||||||
|
"sms_message":"SMS message"
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import Accountant from 'containers/Preferences/Accountant/Accountant';
|
|||||||
// import Accounts from 'containers/Preferences/Accounts/Accounts';
|
// import Accounts from 'containers/Preferences/Accounts/Accounts';
|
||||||
import Currencies from 'containers/Preferences/Currencies/Currencies';
|
import Currencies from 'containers/Preferences/Currencies/Currencies';
|
||||||
import Item from 'containers/Preferences/Item';
|
import Item from 'containers/Preferences/Item';
|
||||||
|
import SMSMessageTemplates from '../containers/Preferences/SMSMessageTemplates';
|
||||||
import DefaultRoute from '../containers/Preferences/DefaultRoute';
|
import DefaultRoute from '../containers/Preferences/DefaultRoute';
|
||||||
|
|
||||||
const BASE_URL = '/preferences';
|
const BASE_URL = '/preferences';
|
||||||
@@ -34,6 +35,11 @@ export default [
|
|||||||
component: Item,
|
component: Item,
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${BASE_URL}/sms-message`,
|
||||||
|
component: SMSMessageTemplates,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `${BASE_URL}/`,
|
path: `${BASE_URL}/`,
|
||||||
component: DefaultRoute,
|
component: DefaultRoute,
|
||||||
|
|||||||
Reference in New Issue
Block a user