diff --git a/src/common/classes.js b/src/common/classes.js
index bea33a718..e1df0b33f 100644
--- a/src/common/classes.js
+++ b/src/common/classes.js
@@ -67,6 +67,7 @@ const CLASSES = {
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',
+ PREFERENCES_PAGE_INSIDE_CONTENT_ROLES_FORM: 'preferences-page__inside-content--roles-form',
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
diff --git a/src/components/Abilities.js b/src/components/Abilities.js
new file mode 100644
index 000000000..152ea01b0
--- /dev/null
+++ b/src/components/Abilities.js
@@ -0,0 +1,10 @@
+import { AbilityBuilder, defineAbility } from '@casl/ability';
+import { createContextualCan } from '@casl/react';
+import { createContext } from 'react';
+
+export const AbilityContext = createContext();
+export const Can = createContextualCan(AbilityContext.Consumer);
+
+export const ability = defineAbility((can, cannot) => {
+ cannot('Item', 'create');
+});
diff --git a/src/components/Can.js b/src/components/Can.js
new file mode 100644
index 000000000..9ba7fe434
--- /dev/null
+++ b/src/components/Can.js
@@ -0,0 +1,4 @@
+import { createCanBoundTo } from '@casl/react';
+import ability from '../components/Config/ability';
+
+export default createCanBoundTo(ability);
\ No newline at end of file
diff --git a/src/components/Config/ability.js b/src/components/Config/ability.js
new file mode 100644
index 000000000..72342959f
--- /dev/null
+++ b/src/components/Config/ability.js
@@ -0,0 +1,10 @@
+import { AbilityBuilder } from '@casl/ability';
+// import { AbilitySubject, ItemAbility } from '../../common/abilityOption';
+
+export function defineAbilitiesFor(role) {
+ const { rules, can } = new AbilityBuilder();
+
+ can('create', 'Item');
+
+ return new Ability(rules);
+}
diff --git a/src/components/DialogsContainer.js b/src/components/DialogsContainer.js
index db9f883c2..871e38a58 100644
--- a/src/components/DialogsContainer.js
+++ b/src/components/DialogsContainer.js
@@ -23,8 +23,9 @@ 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 NotifyPaymentReceiveViaSMSDialog from '../containers/Dialogs/NotifyPaymentReceiveViaSMSDialog';
import SMSMessageDialog from '../containers/Dialogs/SMSMessageDialog';
+import TransactionsLockingDialog from '../containers/Dialogs/TransactionsLockingDialog';
/**
* Dialogs container.
@@ -58,6 +59,7 @@ export default function DialogsContainer() {
+
);
}
diff --git a/src/config/sidebarMenu.js b/src/config/sidebarMenu.js
index ddd6799b3..bb02d9100 100644
--- a/src/config/sidebarMenu.js
+++ b/src/config/sidebarMenu.js
@@ -172,6 +172,10 @@ export default [
text: ,
href: '/manual-journals',
},
+ {
+ text: ,
+ href: '/transactions-locking',
+ },
{
text: ,
href: '/exchange-rates',
diff --git a/src/containers/Alerts/Roles/RoleDeleteAlert.js b/src/containers/Alerts/Roles/RoleDeleteAlert.js
new file mode 100644
index 000000000..c3b5e1946
--- /dev/null
+++ b/src/containers/Alerts/Roles/RoleDeleteAlert.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
+import { Intent, Alert } from '@blueprintjs/core';
+import { AppToaster } from 'components';
+
+import { useDeleteRole } from 'hooks/query';
+
+import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
+import withAlertActions from 'containers/Alert/withAlertActions';
+
+import { compose } from 'utils';
+
+/**
+ * Role delete alert.
+ */
+function RoleDeleteAlert({
+ name,
+
+ // #withAlertStoreConnect
+ isOpen,
+ payload: { roleId },
+
+ // #withAlertActions
+ closeAlert,
+}) {
+ const { mutateAsync: deleteRole, isLoading } = useDeleteRole();
+
+ // Handle cancel delete role alert.
+ const handleCancelDelete = () => {
+ closeAlert(name);
+ };
+
+ // Handle confirm delete role.
+ const handleConfirmDeleteRole = () => {
+ deleteRole(roleId)
+ .then(() => {
+ AppToaster.show({
+ message: intl.get('roles.permission_schema.delete.alert_message'),
+ intent: Intent.SUCCESS,
+ });
+ })
+ .catch(
+ ({
+ response: {
+ data: { errors },
+ },
+ }) => {},
+ )
+ .finally(() => {
+ closeAlert(name);
+ });
+ };
+
+ return (
+ }
+ confirmButtonText={}
+ icon="trash"
+ intent={Intent.DANGER}
+ isOpen={isOpen}
+ onCancel={handleCancelDelete}
+ onConfirm={handleConfirmDeleteRole}
+ loading={isLoading}
+ >
+
+
+
+
+ );
+}
+
+export default compose(
+ withAlertStoreConnect(),
+ withAlertActions,
+)(RoleDeleteAlert);
diff --git a/src/containers/AlertsContainer/registered.js b/src/containers/AlertsContainer/registered.js
index 898c583d3..d0e77fcfb 100644
--- a/src/containers/AlertsContainer/registered.js
+++ b/src/containers/AlertsContainer/registered.js
@@ -16,6 +16,7 @@ import ExpensesAlerts from '../Expenses/ExpensesAlerts';
import AccountTransactionsAlerts from '../CashFlow/AccountTransactions/AccountTransactionsAlerts';
import UsersAlerts from '../Preferences/Users/UsersAlerts';
import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts';
+import RolesAlerts from '../Preferences/Users/Roles/RolesAlerts';
export default [
...AccountsAlerts,
@@ -36,4 +37,5 @@ export default [
...AccountTransactionsAlerts,
...UsersAlerts,
...CurrenciesAlerts,
+ ...RolesAlerts,
];
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingDialogContent.js b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingDialogContent.js
new file mode 100644
index 000000000..28d763962
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingDialogContent.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import { TransactionsLockingFormProvider } from './TransactionsLockingFormProvider';
+import TransactionsLockingForm from './TransactionsLockingForm';
+
+export default function TransactionsLockingDialogContent({
+ // #ownProps
+ dialogName,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFloatingActions.js b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFloatingActions.js
new file mode 100644
index 000000000..51c29dedc
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFloatingActions.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Intent, Button, Classes } from '@blueprintjs/core';
+import { useFormikContext } from 'formik';
+import { FormattedMessage as T } from 'components';
+
+import { useTransactionLockingContext } from './TransactionsLockingFormProvider';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+/**
+ * Transactions locking floating actions.
+ */
+function TransactionsLockingFloatingActions({
+ // #withDialogActions
+ closeDialog,
+}) {
+ // Formik context.
+ const { isSubmitting } = useFormikContext();
+
+ const { dialogName } = useTransactionLockingContext();
+
+ // Handle cancel button click.
+ const handleCancelBtnClick = (event) => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(TransactionsLockingFloatingActions);
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingForm.js b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingForm.js
new file mode 100644
index 000000000..9bba5437a
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingForm.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import moment from 'moment';
+import { Intent } from '@blueprintjs/core';
+import { Formik } from 'formik';
+import intl from 'react-intl-universal';
+
+import '../../../style/pages/TransactionsLocking/TransactionsLockingDialog.scss'
+
+import { AppToaster } from 'components';
+import { CreateTransactionsLockingFormSchema } from './TransactionsLockingForm.schema';
+
+import { useTransactionLockingContext } from './TransactionsLockingFormProvider';
+import TransactionsLockingFormContent from './TransactionsLockingFormContent';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+const defaultInitialValues = {
+ date: moment(new Date()).format('YYYY-MM-DD'),
+ reason: '',
+};
+
+/**
+ * Transactions Locking From.
+ */
+function TransactionsLockingForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const { dialogName } = useTransactionLockingContext();
+ // Initial form values.
+ const initialValues = {
+ ...defaultInitialValues,
+ };
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {};
+
+ return (
+
+ );
+}
+export default compose(withDialogActions)(TransactionsLockingForm);
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingForm.schema.js b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingForm.schema.js
new file mode 100644
index 000000000..a74058f3f
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingForm.schema.js
@@ -0,0 +1,13 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+
+const Schema = Yup.object().shape({
+ date: Yup.date().required().label(intl.get('date')),
+ reason: Yup.string()
+ .required()
+ .min(3)
+ .max(DATATYPES_LENGTH.TEXT)
+ .label(intl.get('reason')),
+});
+export const CreateTransactionsLockingFormSchema = Schema;
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormContent.js b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormContent.js
new file mode 100644
index 000000000..43a1e1dda
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormContent.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Form } from 'formik';
+
+import TransactionsLockingFormFields from './TransactionsLockingFormFields';
+import TransactionsLockingFloatingActions from './TransactionsLockingFloatingActions';
+
+/**
+ * Transactions locking form content.
+ */
+export default function TransactionsLockingFormContent() {
+ return (
+
+ );
+}
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormFields.js b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormFields.js
new file mode 100644
index 000000000..bede9d2fc
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormFields.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import { FastField, ErrorMessage } from 'formik';
+import { Classes, FormGroup, TextArea, Position } from '@blueprintjs/core';
+import { DateInput } from '@blueprintjs/datetime';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import { FieldRequiredHint, FormattedMessage as T } from 'components';
+import { useAutofocus } from 'hooks';
+import {
+ inputIntent,
+ momentFormatter,
+ tansformDateValue,
+ handleDateChange,
+} from 'utils';
+
+/**
+ * Transactions locking form fields.
+ */
+export default function TransactionsLockingFormFields() {
+ const dateFieldRef = useAutofocus();
+
+ return (
+
+ {/*------------ Date -----------*/}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ minimal={true}
+ className={classNames(CLASSES.FILL, 'form-group--date')}
+ >
+ {
+ form.setFieldValue('date', formattedDate);
+ })}
+ value={tansformDateValue(value)}
+ popoverProps={{
+ position: Position.BOTTOM,
+ minimal: true,
+ }}
+ intent={inputIntent({ error, touched })}
+ inputRef={(ref) => (dateFieldRef.current = ref)}
+ />
+
+ )}
+
+ {/*------------ reasons -----------*/}
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ className={'form-group--reason'}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ )}
+
+
+ );
+}
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormProvider.js b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormProvider.js
new file mode 100644
index 000000000..7a8d95b96
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/TransactionsLockingFormProvider.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { DialogContent } from 'components';
+
+const TransactionsLockingContext = React.createContext();
+
+function TransactionsLockingFormProvider({ dialogName, ...props }) {
+ // State provider.
+ const provider = {
+ dialogName,
+ };
+ return (
+
+
+
+ );
+}
+
+const useTransactionLockingContext = () =>
+ React.useContext(TransactionsLockingContext);
+
+export { TransactionsLockingFormProvider, useTransactionLockingContext };
diff --git a/src/containers/Dialogs/TransactionsLockingDialog/index.js b/src/containers/Dialogs/TransactionsLockingDialog/index.js
new file mode 100644
index 000000000..d6ff27db8
--- /dev/null
+++ b/src/containers/Dialogs/TransactionsLockingDialog/index.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose } from 'utils';
+
+const TransactionsLockingContent = React.lazy(() =>
+ import('./TransactionsLockingDialogContent'),
+);
+
+/**
+ * Transaction Locking dialog
+ */
+function TransactionsLockingDialog({ dialogName, payload = {}, isOpen }) {
+ return (
+ }
+ canEscapeKeyClose={true}
+ isOpen={isOpen}
+ className={'dialog--transaction--locking'}
+ >
+
+
+
+
+ );
+}
+
+export default compose(withDialogRedux())(TransactionsLockingDialog);
diff --git a/src/containers/Items/ItemsActionsBar.js b/src/containers/Items/ItemsActionsBar.js
index 67e30c732..e882ac19d 100644
--- a/src/containers/Items/ItemsActionsBar.js
+++ b/src/containers/Items/ItemsActionsBar.js
@@ -30,6 +30,9 @@ import withSettings from '../Settings/withSettings';
import { compose } from 'utils';
import withSettingsActions from '../Settings/withSettingsActions';
+
+import { Can, AbilityContext } from '../../components/Abilities';
+
/**
* Items actions bar.
*/
@@ -57,6 +60,8 @@ function ItemsActionsBar({
// Items refresh action.
const { refresh } = useRefreshItems();
+ const { ability } = React.useContext(AbilityContext);
+
// History context.
const history = useHistory();
@@ -101,13 +106,14 @@ function ItemsActionsBar({
/>
- }
- text={}
- onClick={onClickNewItem}
- />
-
+ {/* */}
+ }
+ text={}
+ onClick={onClickNewItem}
+ />
+ {/* */}
+ import('../../../Alerts/Roles/RoleDeleteAlert'),
+);
+
+/**
+ * Roles alerts
+ */
+export default [{ name: 'role-delete', component: RoleDeleteAlert }];
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.js b/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.js
new file mode 100644
index 000000000..37f352b2b
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { Formik } from 'formik';
+import { defaultTo, sumBy, isEmpty } from 'lodash';
+
+import 'style/pages/Preferences/Roles/Form.scss';
+
+import { Intent } from '@blueprintjs/core';
+
+import { AppToaster, FormattedMessage as T } from 'components';
+
+import { CreateRolesFormSchema, EditRolesFormSchema } from './RolesForm.schema';
+
+import { useRolesFormContext } from './RolesFormProvider';
+import { transformToArray } from './utils';
+
+import RolesFormContent from './RolesFormContent';
+import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+
+import { compose, transformToForm } from 'utils';
+
+const defaultValues = {
+ role_name: '',
+ role_description: '',
+ permissions: {},
+};
+
+/**
+ * Preferences - Roles Form.
+ */
+function RolesForm({
+ // #withDashboardActions
+ changePreferencesPageTitle,
+}) {
+ const {
+ isNewMode,
+ createRolePermissionMutate,
+ editRolePermissionMutate,
+ permissionSchema,
+ roleId,
+ } = useRolesFormContext();
+
+ // Initial values.
+ const initialValues = {
+ ...defaultValues,
+ ...transformToForm(permissionSchema, defaultValues),
+ };
+
+ React.useEffect(() => {
+ changePreferencesPageTitle();
+ }, [changePreferencesPageTitle]);
+
+ const handleFormSubmit = (values, { setSubmitting }) => {
+ const permission = transformToArray(values);
+ const form = {
+ ...values,
+ permissions: permission,
+ };
+ setSubmitting(true);
+ const onSuccess = () => {
+ AppToaster.show({
+ message: intl.get(
+ isNewMode
+ ? 'roles.permission_schema.success_message'
+ : 'roles.permission_schema.upload_message',
+ ),
+ intent: Intent.SUCCESS,
+ });
+ setSubmitting(false);
+ };
+
+ const onError = (errors) => {
+ setSubmitting(false);
+ };
+ if (isNewMode) {
+ createRolePermissionMutate(form).then(onSuccess).catch(onError);
+ } else {
+ editRolePermissionMutate([roleId, form]).then(onSuccess).catch(onError);
+ }
+ };
+
+ return (
+
+
+
+ );
+}
+
+export default compose(withDashboardActions)(RolesForm);
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.schema.js b/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.schema.js
new file mode 100644
index 000000000..ab1012778
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.schema.js
@@ -0,0 +1,17 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+
+const Schema = Yup.object().shape({
+ role_name: Yup.string().required().label(intl.get('roles.label.role_name')),
+ role_description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
+
+ permissions: Yup.object().shape({
+ subject: Yup.string(),
+ ability: Yup.string(),
+ value: Yup.boolean(),
+ }),
+});
+
+export const CreateRolesFormSchema = Schema;
+export const EditRolesFormSchema = Schema;
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/RolesFormContent.js b/src/containers/Preferences/Users/Roles/RolesForm/RolesFormContent.js
new file mode 100644
index 000000000..ae2267054
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesForm/RolesFormContent.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { ErrorMessage, FastField, Form, useFormikContext } from 'formik';
+import {
+ Button,
+ FormGroup,
+ InputGroup,
+ Intent,
+ TextArea,
+} from '@blueprintjs/core';
+import { inputIntent } from 'utils';
+import { FormattedMessage as T, FieldRequiredHint } from 'components';
+import { useAutofocus } from 'hooks';
+import { RolesPermissionList } from './components';
+
+/**
+ * Preferences - Roles Form content.
+ */
+export default function RolesFormContent() {
+ const history = useHistory();
+
+ const { isSubmitting, values } = useFormikContext();
+ const roleNameFieldRef = useAutofocus();
+
+ const handleCloseClick = () => {
+ history.go(-1);
+ };
+ return (
+
+ );
+}
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/RolesFormPage.js b/src/containers/Preferences/Users/Roles/RolesForm/RolesFormPage.js
new file mode 100644
index 000000000..a307d9195
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesForm/RolesFormPage.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import RolesForm from './RolesForm';
+import { RolesFormProvider } from './RolesFormProvider';
+
+/**
+ * Roles Form page.
+ */
+export default function RolesFormPage() {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/RolesFormProvider.js b/src/containers/Preferences/Users/Roles/RolesForm/RolesFormProvider.js
new file mode 100644
index 000000000..3e8cb2982
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesForm/RolesFormProvider.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import _, { isArray } from 'lodash';
+
+import {
+ useCreateRolePermissionSchema,
+ useEditRolePermissionSchema,
+ usePermissionsSchema,
+ useRolePermission,
+} from 'hooks/query';
+import PreferencesPageLoader from '../../../PreferencesPageLoader';
+import { transformToObject } from './utils';
+
+const RolesFormContext = React.createContext();
+
+/**
+ * Roles Form page provider.
+ */
+function RolesFormProvider({ roleId, ...props }) {
+ // Create and edit roles mutations.
+ const { mutateAsync: createRolePermissionMutate } =
+ useCreateRolePermissionSchema();
+
+ const { mutateAsync: editRolePermissionMutate } =
+ useEditRolePermissionSchema();
+
+ const {
+ data: permissionsSchema,
+ isLoading: isPermissionsSchemaLoading,
+ isFetching: isPermissionsSchemaFetching,
+ } = usePermissionsSchema();
+
+ // const roleId = 6;
+
+ const { data: permission, isLoading: isPermissionLoading } =
+ useRolePermission(roleId, {
+ enabled: !!roleId,
+ });
+
+ const isNewMode = !roleId;
+
+ const permissionSchema = transformToObject(permission);
+
+ // Provider state.
+ const provider = {
+ isNewMode,
+ roleId,
+ permissionsSchema,
+ permissionSchema,
+ isPermissionsSchemaLoading,
+ isPermissionsSchemaFetching,
+ createRolePermissionMutate,
+ editRolePermissionMutate,
+ };
+
+ return (
+
+
+ {isPermissionsSchemaLoading || isPermissionLoading ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+const useRolesFormContext = () => React.useContext(RolesFormContext);
+
+export { RolesFormProvider, useRolesFormContext };
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/components.js b/src/containers/Preferences/Users/Roles/RolesForm/components.js
new file mode 100644
index 000000000..75b7921df
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesForm/components.js
@@ -0,0 +1,150 @@
+import React from 'react';
+import { Checkbox } from '@blueprintjs/core';
+import styled from 'styled-components';
+import { castArray } from 'lodash';
+
+import { FastField, useFormikContext } from 'formik';
+import { whenRtl, whenLtr } from 'utils/styled-components';
+import { Icon, Hint, If, Choose } from 'components';
+import { useRolesFormContext } from './RolesFormProvider';
+
+const RoleLabelCheckbox = ({ subject, label, description }) => (
+ <>
+
+ {/*------------- subject checbox ------------- */}
+
+ {({ form: { setFieldValue, values }, field }) => (
+
+ )}
+
+ {description}
+
+ >
+);
+
+const AbilitiesList = ({ subject, abilities }) => {
+ return (
+
+ {abilities?.map(({ key, label }) => (
+
+ {({ form: { setFieldValue, values }, field }) => (
+
+ )}
+
+ ))}
+
+ );
+};
+
+const ExtraAbilitiesList = ({ subject, extraAbilities }) => {
+ return extraAbilities?.map(({ key, label }) => (
+
+
+ {({ form: { setFieldValue, values }, field }) => (
+
+ )}
+
+
+ ));
+};
+
+export const RolesPermissionList = () => {
+ const { permissionsSchema } = useRolesFormContext();
+
+ return (
+
+
+ {permissionsSchema.map(
+ ({
+ subject,
+ subject_label,
+ description,
+ abilities,
+ extra_abilities,
+ }) => {
+ const extraAbilitiesList = Array.isArray(extra_abilities)
+ ? extra_abilities
+ : [];
+
+ const abilitiesList = castArray(abilities) ? abilities : [];
+
+ return (
+
+
+
+
+
+
+
+
+ );
+ },
+ )}
+
+
+ );
+};
+
+const GroupList = styled.div`
+ list-style: none;
+ border: 1px solid #d2dce2;
+ border-radius: 6px;
+ font-size: 13px;
+
+ ul:first-child > li:last-child {
+ border-bottom: 0;
+ border-top: 0;
+ }
+`;
+
+const BoxedGroupList = styled.ul`
+ margin: 0;
+ list-style: none;
+`;
+
+const RoleList = styled.li`
+ display: block;
+ padding: 5px 10px;
+ margin: 0;
+ line-height: 20px;
+ border-bottom: 1px solid #e0e0e0;
+`;
+
+const LabelCheckbox = styled.label`
+ > * {
+ display: inline-block;
+ }
+ .block {
+ width: 220px;
+ padding: 2px 0;
+ font-weight: 500;
+ }
+`;
+
+const AbilitieList = styled.ul`
+ list-style: none;
+ /* margin-left: 12px; // 10px */
+ margin: 0px 10px 0px;
+
+ > li {
+ display: inline-block;
+ margin-top: 3px;
+ }
+`;
+
+const AbilitiesChildList = styled.li`
+ display: inline-block;
+ margin-top: 3px;
+`;
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/utils.js b/src/containers/Preferences/Users/Roles/RolesForm/utils.js
new file mode 100644
index 000000000..bdb6ef36a
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesForm/utils.js
@@ -0,0 +1,27 @@
+import { isEmpty } from 'lodash';
+
+export const transformToArray = ({ permissions }) => {
+ return Object.keys(permissions).map((index) => {
+ const [value, key] = index.split('/');
+
+ return {
+ subject: value,
+ ability: key,
+ value: permissions[index],
+ };
+ });
+};
+
+export const transformToObject = ({ name, description, permissions }) => {
+ if (!isEmpty(permissions)) {
+ const output = {};
+ permissions.forEach((item) => {
+ output[`${item.subject}/${item.ability}`] = !!item.value;
+ });
+ return {
+ role_name: name,
+ role_description: description,
+ permissions: { ...output },
+ };
+ }
+};
diff --git a/src/containers/Preferences/Users/Roles/RolesLanding/RolesDataTable.js b/src/containers/Preferences/Users/Roles/RolesLanding/RolesDataTable.js
new file mode 100644
index 000000000..bfc5ee67a
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesLanding/RolesDataTable.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { DataTable } from 'components';
+import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
+
+import { useRolesTableColumns, ActionsMenu } from './components';
+import withAlertsActions from 'containers/Alert/withAlertActions';
+
+import { compose } from 'utils';
+
+/**
+ * Roles data table.
+ */
+function RolesDataTable({
+ // #withAlertsActions
+ openAlert,
+}) {
+ const columns = useRolesTableColumns();
+
+ const handleDeleteRole = ({ id }) => {
+ openAlert('role-delete', { roleId: id });
+ };
+
+ // const Data = [{ name: 'AH', description: 'Description' }];
+ return (
+
+ );
+}
+
+export default compose(withAlertsActions)(RolesDataTable);
diff --git a/src/containers/Preferences/Users/Roles/RolesLanding/RolesList.js b/src/containers/Preferences/Users/Roles/RolesLanding/RolesList.js
new file mode 100644
index 000000000..436adb6e5
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesLanding/RolesList.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { RolesListProvider } from './RolesListProvider';
+import RolesDataTable from './RolesDataTable';
+
+/**
+ * Roles list.
+ */
+function RolesListPrefernces() {
+ return (
+
+
+
+ );
+}
+
+export default RolesListPrefernces;
diff --git a/src/containers/Preferences/Users/Roles/RolesLanding/RolesListProvider.js b/src/containers/Preferences/Users/Roles/RolesLanding/RolesListProvider.js
new file mode 100644
index 000000000..248b47260
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesLanding/RolesListProvider.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+// import {} from 'hooks/query';
+
+const RolesListContext = React.createContext();
+
+/**
+ * Roles list provider.
+ */
+function RolesListProvider({ ...props }) {
+ // Provider state.
+ const provider = {};
+ return (
+
+
+
+ );
+}
+
+const useRolesContext = () => React.useContext(RolesListContext);
+
+export { RolesListProvider, useRolesContext };
diff --git a/src/containers/Preferences/Users/Roles/RolesLanding/components.js b/src/containers/Preferences/Users/Roles/RolesLanding/components.js
new file mode 100644
index 000000000..2ef284188
--- /dev/null
+++ b/src/containers/Preferences/Users/Roles/RolesLanding/components.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { Intent, Button, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
+import { safeCallback } from 'utils';
+import { Icon, If } from 'components';
+
+/**
+ * Context menu of roles.
+ */
+export function ActionsMenu({ payload: { onDeleteRole }, row: { original } }) {
+ return (
+
+ );
+}
+
+/**
+ * Retrieve Roles table columns.
+ * @returns
+ */
+export function useRolesTableColumns() {
+ return React.useMemo(
+ () => [
+ {
+ id: 'name',
+ Header: intl.get('roles.column.name'),
+ accessor: 'name',
+ className: 'name',
+ width: '100',
+ disableSortBy: true,
+ },
+ {
+ id: 'description',
+ Header: intl.get('roles.column.description'),
+ accessor: 'description',
+ className: 'description',
+ width: '120',
+ disableSortBy: true,
+ },
+ ],
+ [],
+ );
+}
diff --git a/src/containers/Preferences/Users/RolesList.js b/src/containers/Preferences/Users/RolesList.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/containers/Preferences/Users/Users.js b/src/containers/Preferences/Users/Users.js
index ed5e9a806..4f453d0b6 100644
--- a/src/containers/Preferences/Users/Users.js
+++ b/src/containers/Preferences/Users/Users.js
@@ -26,11 +26,18 @@ function UsersPreferences({ openDialog }) {
);
diff --git a/src/containers/Preferences/Users/UsersActions.js b/src/containers/Preferences/Users/UsersActions.js
index ada5c76af..3575b6ee9 100644
--- a/src/containers/Preferences/Users/UsersActions.js
+++ b/src/containers/Preferences/Users/UsersActions.js
@@ -1,40 +1,41 @@
import React from 'react';
-import {
- Button,
- Intent,
-} from '@blueprintjs/core';
+import { useHistory } from 'react-router-dom';
+
+import { Button, Intent } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import Icon from 'components/Icon';
import withDialogActions from 'containers/Dialog/withDialogActions';
-import {compose} from 'utils';
+import { compose } from 'utils';
-function UsersActions({
- openDialog,
- closeDialog,
-}) {
+function UsersActions({ openDialog, closeDialog }) {
+ const history = useHistory();
const onClickNewUser = () => {
openDialog('invite-user');
};
+ const onClickNewRole = () => {
+ history.push('/preferences/roles');
+ };
+
return (
}
+ icon={}
onClick={onClickNewUser}
- intent={Intent.PRIMARY}>
+ intent={Intent.PRIMARY}
+ >
}
- onClick={onClickNewUser}>
+ icon={}
+ onClick={onClickNewRole}
+ >
);
}
-export default compose(
- withDialogActions,
-)(UsersActions);
\ No newline at end of file
+export default compose(withDialogActions)(UsersActions);
diff --git a/src/containers/TransactionsLocking/TransactionsLockingList.js b/src/containers/TransactionsLocking/TransactionsLockingList.js
new file mode 100644
index 000000000..6163b8daf
--- /dev/null
+++ b/src/containers/TransactionsLocking/TransactionsLockingList.js
@@ -0,0 +1,86 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import styled from 'styled-components';
+import clsx from 'classnames';
+import { TransactionsLockingProvider } from './TransactionsLockingProvider';
+import { TransactionLockingContent } from './components';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+
+import { compose } from 'utils';
+
+const DataTest = [
+ {
+ name: 'sales',
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do',
+ },
+ {
+ name: 'purchases',
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do',
+ },
+ {
+ name: 'financial',
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do',
+ },
+];
+
+function Paragraph({ className, children }) {
+ return {children}
;
+}
+
+/**
+ * Transactions locking list.
+ */
+function TransactionsLockingList({
+ // #withDialogActions
+ openDialog,
+}) {
+ // Handle switch transactions locking.
+ const handleSwitchTransactionsLocking = () => {
+ openDialog('transactions-locking', {});
+ };
+
+ return (
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+ Lock All Transactions At Once. {' '}
+ {''}Lock All Transactions At Once →
+
+
+ {DataTest.map(({ name, description }) => (
+
+ ))}
+
+
+ );
+}
+export default compose(withDialogActions)(TransactionsLockingList);
+
+const TransactionsLocking = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 32px;
+ max-width: 700px;
+`;
+
+const TransactionsLockingParagraph = styled(Paragraph)`
+ margin-bottom: 30px;
+`;
+
+const TransLockingTitle = styled.h2`
+ margin-bottom: 12px;
+`;
+
+const TransLockingDesc = styled.p``;
diff --git a/src/containers/TransactionsLocking/TransactionsLockingProvider.js b/src/containers/TransactionsLocking/TransactionsLockingProvider.js
new file mode 100644
index 000000000..33f1b855c
--- /dev/null
+++ b/src/containers/TransactionsLocking/TransactionsLockingProvider.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+
+const TransactionsLockingContext = React.createContext();
+
+/**
+ * Transactions locking data provider.
+ */
+function TransactionsLockingProvider({ ...props }) {
+ // Provider
+ const provider = {};
+
+ return (
+
+
+
+ );
+}
+
+const useTransactionsLockingContext = () =>
+ React.useContext(TransactionsLockingContext);
+
+export { TransactionsLockingProvider, useTransactionsLockingContext };
diff --git a/src/containers/TransactionsLocking/components.js b/src/containers/TransactionsLocking/components.js
new file mode 100644
index 000000000..bd6825f41
--- /dev/null
+++ b/src/containers/TransactionsLocking/components.js
@@ -0,0 +1,79 @@
+import React from 'react';
+import styled from 'styled-components';
+import { Switch, FormGroup, Position } from '@blueprintjs/core';
+import { Hint, Icon, FormattedMessage as T } from 'components';
+
+export const TransactionLockingContent = ({ name, description, onSwitch }) => (
+
+
+
+
+
+
+
+
+ {' '}
+
+
+ {description}
+
+
+
+
+
+
+);
+
+const TransactionLockingWrapp = styled.div`
+ display: flex;
+ align-items: center;
+ border-radius: 6px;
+ border: 1px solid #d1dee2;
+ padding: 14px 18px;
+ margin-bottom: 25px;
+ background: #fff;
+
+ div.block {
+ flex: 1 1 0;
+ margin-left: 20px;
+ width: 100%;
+ }
+`;
+
+const TransactionsLockingcontent = styled.div`
+ display: flex;
+ align-items: center;
+ flex: 1 1 0;
+`;
+
+const TransLockingItemTitle = styled.h1`
+ font-size: 18px;
+ margin: 0 0 8px;
+ line-height: 1;
+ font-weight: 600;
+`;
+const TransLockingItemDesc = styled.p`
+ margin-bottom: 0;
+ opacity: 0.8;
+`;
+
+const TransLockingIcon = styled.div`
+ border: 1px solid #d2dde2;
+ height: 50px;
+ width: 50px;
+ text-align: center;
+ line-height: 50px;
+ border-radius: 5px;
+ color: #8190ac;
+`;
diff --git a/src/hooks/query/index.js b/src/hooks/query/index.js
index ca18d976d..e73c09307 100644
--- a/src/hooks/query/index.js
+++ b/src/hooks/query/index.js
@@ -29,3 +29,4 @@ export * from './GenericResource';
export * from './jobs';
export * from './misc';
export * from './cashflowAccounts'
+export * from './roles'
diff --git a/src/hooks/query/roles.js b/src/hooks/query/roles.js
new file mode 100644
index 000000000..7d45fac4e
--- /dev/null
+++ b/src/hooks/query/roles.js
@@ -0,0 +1,95 @@
+import { useMutation, useQueryClient } from 'react-query';
+import { useRequestQuery } from '../useQueryRequest';
+import useApiRequest from '../useRequest';
+import t from './types';
+
+// Common invalidate queries.
+const commonInvalidateQueries = (queryClient) => {
+ queryClient.invalidateQueries(t.ROLES_PERMISSIONS_SCHEMA);
+ queryClient.invalidateQueries(t.ROLE);
+};
+
+/**
+ * Edit role .
+ */
+export function useEditRolePermissionSchema(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(([id, values]) => apiRequest.post(`roles/${id}`, values), {
+ onSuccess: () => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ });
+}
+
+/**
+ * Create a new roles
+ */
+export function useCreateRolePermissionSchema(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation((values) => apiRequest.post(`roles`, values), {
+ onSuccess: () => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ });
+}
+
+/**
+ * Delete the given role.
+ */
+export function useDeleteRole(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation((id) => apiRequest.delete(`roles/${id}`), {
+ onSuccess: (res, id) => {
+ // Invalidate specific role.
+ queryClient.invalidateQueries(t.ROLE, id);
+
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ });
+}
+
+/**
+ * Retrive the roles permissions schema.
+ */
+export function usePermissionsSchema(query, props) {
+ return useRequestQuery(
+ [t.ROLES_PERMISSIONS_SCHEMA, query],
+ { method: 'get', url: 'roles/permissions/schema', params: query },
+ {
+ select: (res) => res.data.data,
+ defaultData: {
+ roles: [],
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Retrieve the role permisstion schema.
+ * @param {number} role_id - role id.
+ */
+export function useRolePermission(role_id, props, requestProps) {
+ return useRequestQuery(
+ [t.ROLE, role_id],
+ { method: 'get', url: `roles/${role_id}`, ...requestProps },
+ {
+ select: (res) => res.data.role,
+ defaultData: {
+ permission: [],
+ },
+ ...props,
+ },
+ );
+}
diff --git a/src/hooks/query/types.js b/src/hooks/query/types.js
index 1aebbd8e1..edec96183 100644
--- a/src/hooks/query/types.js
+++ b/src/hooks/query/types.js
@@ -102,6 +102,12 @@ const USERS = {
USER: 'USER',
};
+const ROLES = {
+ ROLE: 'ROLE',
+ ROLES: 'ROLES',
+ ROLES_PERMISSIONS_SCHEMA: 'ROLES_PERMISSIONS_SCHEMA',
+};
+
const SETTING = {
SETTING: 'SETTING',
SETTING_INVOICES: 'SETTING_INVOICES',
@@ -177,4 +183,5 @@ export default {
...LANDED_COSTS,
...CONTACTS,
...CASH_FLOW_ACCOUNTS,
+ ...ROLES,
};
diff --git a/src/lang/en/index.json b/src/lang/en/index.json
index 3a1f34b77..e083506be 100644
--- a/src/lang/en/index.json
+++ b/src/lang/en/index.json
@@ -1467,5 +1467,17 @@
"sms_notification.payment_details.type": "Payment receive thank you.",
"sms_notification.receipt_details.type": "Sale receipt details",
"personal": "Personal",
- "list.create":"Create {value}"
+ "list.create":"Create {value}",
+ "roles.label":"Roles",
+ "roles.column.name":"Name",
+ "roles.column.description":"description",
+ "roles.edit_roles":"Edit Roles",
+ "roles.delete_roles":"Delete Roles",
+ "roles.label.role_name":"Role name",
+ "sidebar.transactions_locaking":"Transactions Locaking",
+ "transactions_locking.dialog.label":"Transactions locking",
+ "roles.permission_schema.success_message":"The role has been created successfully.",
+ "roles.permission_schema.upload_message":"The given role hsa been updated successfully.",
+ "roles.permission_schema.delete.alert_message":"The given role has been deleted successfully.",
+ "roles.permission_schema.once_delete_this_role_you_will_able_to_restore_it":"Once you delete this role, you won't be able to restore it later. Are you sure you want to delete this role?"
}
\ No newline at end of file
diff --git a/src/routes/dashboard.js b/src/routes/dashboard.js
index f0c2097b1..dd9764664 100644
--- a/src/routes/dashboard.js
+++ b/src/routes/dashboard.js
@@ -785,6 +785,13 @@ export const getDashboardRoutes = () => [
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
defaultSearchResource: RESOURCES_TYPES.ACCOUNT,
},
+ {
+ path: `/transactions-locking`,
+ component: lazy(() =>
+ import('../containers/TransactionsLocking/TransactionsLockingList'),
+ ),
+ pageTitle: intl.get('sidebar.transactions_locaking'),
+ },
// Homepage
{
path: `/`,
diff --git a/src/routes/preferences.js b/src/routes/preferences.js
index 98ff2e46b..9fc454ddd 100644
--- a/src/routes/preferences.js
+++ b/src/routes/preferences.js
@@ -1,5 +1,6 @@
import General from 'containers/Preferences/General/General';
-import Users from 'containers/Preferences/Users/Users';
+import Users from '../containers/Preferences/Users/Users';
+import Roles from '../containers/Preferences/Users/Roles/RolesForm/RolesFormPage';
import Accountant from 'containers/Preferences/Accountant/Accountant';
// import Accounts from 'containers/Preferences/Accounts/Accounts';
import Currencies from 'containers/Preferences/Currencies/Currencies';
@@ -20,6 +21,16 @@ export default [
component: Users,
exact: true,
},
+ {
+ path: `${BASE_URL}/roles`,
+ component: Roles,
+ exact: true,
+ },
+ {
+ path: `${BASE_URL}/roles/:id`,
+ component: Roles,
+ exact: true,
+ },
{
path: `${BASE_URL}/currencies`,
component: Currencies,
diff --git a/src/routes/preferencesTabs.js b/src/routes/preferencesTabs.js
index 48537aa24..32b377899 100644
--- a/src/routes/preferencesTabs.js
+++ b/src/routes/preferencesTabs.js
@@ -1,30 +1,20 @@
// import AccountsCustomFields from "containers/Preferences/AccountsCustomFields";
-import UsersList from 'containers/Preferences/Users/UsersList';
-import RolesList from 'containers/Preferences/Users/RolesList';
+import UsersList from '../containers/Preferences/Users/UsersList';
+import RolesList from '../containers/Preferences/Users/Roles/RolesLanding/RolesList';
export default {
- accounts: [
- // {
- // path: '',
- // component: AccountsCustomFields,
- // exact: true,
- // },
- // {
- // path: 'custom_fields',
- // component: AccountsCustomFields,
- // exact: true,
- // },
- ],
users: [
{
path: '',
component: UsersList,
exact: true,
},
+ ],
+ roles: [
{
- path: '/roles',
+ path: '',
component: RolesList,
exact: true,
},
],
-}
\ No newline at end of file
+};
diff --git a/src/static/json/icons.js b/src/static/json/icons.js
index fc6ffe780..45bbfb4e6 100644
--- a/src/static/json/icons.js
+++ b/src/static/json/icons.js
@@ -509,11 +509,17 @@ export default {
],
viewBox: '0 0 24 24',
},
- "sms-message-preview": {
+ '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"
+ 'M1199.9485,803.1623',
],
- viewBox: "0 0 407.6681 375.3573",
- }
+ viewBox: '0 0 407.6681 375.3573',
+ },
+ lock: {
+ path: [
+ 'M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z',
+ ],
+ viewBox: '0 0 24 24',
+ },
};
diff --git a/src/style/pages/Preferences/Roles/Form.scss b/src/style/pages/Preferences/Roles/Form.scss
new file mode 100644
index 000000000..916d611c2
--- /dev/null
+++ b/src/style/pages/Preferences/Roles/Form.scss
@@ -0,0 +1,58 @@
+// Roles Form page
+//---------------------------------
+.preferences-page__inside-content--roles-form {
+ .card {
+ padding: 25px;
+
+ .card__footer {
+ padding-top: 16px;
+ border-top: 1px solid #e0e7ea;
+ margin-top: 30px;
+
+ .bp3-button {
+ min-width: 65px;
+
+ + .bp3-button {
+ margin-left: 10px;
+ }
+ }
+ }
+ }
+
+ .bp3-form-group {
+ max-width: 500px;
+ margin-bottom: 14px;
+
+ &.bp3-inline {
+ .bp3-label {
+ min-width: 100px;
+ }
+ }
+ .bp3-form-content {
+ width: 100%;
+ }
+ }
+ .form-group--description {
+ textarea {
+ width: 100%;
+ min-width: 100%;
+ font-size: 14px;
+ }
+ }
+ .bp3-control.bp3-checkbox {
+ .bp3-control-indicator {
+ border: 1px solid #c2c2c2;
+ cursor: auto;
+
+ &,
+ &:hover {
+ height: 15px;
+ width: 15px;
+ }
+ &:before {
+ width: 15px;
+ height: 15px;
+ }
+ }
+ }
+}
diff --git a/src/style/pages/Preferences/Users.scss b/src/style/pages/Preferences/Users.scss
index 9046fb9f1..c9a3726e7 100644
--- a/src/style/pages/Preferences/Users.scss
+++ b/src/style/pages/Preferences/Users.scss
@@ -1,12 +1,9 @@
-
// Users/Roles List.
// ---------------------------------
-.preferences-page__inside-content--users{
-
-.bigcapital-datatable {
-
- .td{
- .avatar{
+.preferences-page__inside-content--users {
+ .bigcapital-datatable {
+ .td {
+ .avatar {
display: block;
height: 28px;
width: 28px;
@@ -23,8 +20,13 @@
text-transform: uppercase;
}
- .tr:last-child .td{
+ .tr:last-child .td {
border-bottom: 0;
}
}
+ .bp3-tabs {
+ .bp3-tab-panel {
+ margin-top: 0;
+ }
+ }
}
diff --git a/src/style/pages/TransactionsLocking/TransactionsLockingDialog.scss b/src/style/pages/TransactionsLocking/TransactionsLockingDialog.scss
new file mode 100644
index 000000000..b1aed0430
--- /dev/null
+++ b/src/style/pages/TransactionsLocking/TransactionsLockingDialog.scss
@@ -0,0 +1,27 @@
+.dialog--transaction--locking {
+ max-width: 400px;
+ .bp3-form-group {
+ margin-bottom: 15px;
+ margin-top: 15px;
+
+ label.bp3-label {
+ font-size: 13px;
+ margin-bottom: 3px;
+ }
+ }
+
+ .form-group {
+ &--reason {
+ .bp3-form-content {
+ textarea {
+ width: 100%;
+ min-width: 100%;
+ font-size: 14px;
+ }
+ }
+ }
+ }
+ .bp3-dialog-footer {
+ padding-top: 10px;
+ }
+}