mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
feat: permissions service full access.
This commit is contained in:
@@ -11,9 +11,13 @@ import { FormattedMessage as T } from 'components';
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
export function RoleFormFloatingActions() {
|
||||
// Formik form context.
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
|
||||
// Handle close click.
|
||||
const handleCloseClick = () => {
|
||||
history.go(-1);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { ErrorMessage, FastField } from 'formik';
|
||||
import { FormGroup, InputGroup, TextArea } from '@blueprintjs/core';
|
||||
|
||||
import { inputIntent } from 'utils';
|
||||
import { FormattedMessage as T, FieldRequiredHint, Card } from 'components';
|
||||
import { useAutofocus } from 'hooks';
|
||||
|
||||
/**
|
||||
* Role form header.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
export function RoleFormHeader() {
|
||||
const roleNameFieldRef = useAutofocus();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{/* ---------- name ---------- */}
|
||||
<FastField name={'role_name'}>
|
||||
{({ field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={
|
||||
<strong>
|
||||
<T id={'role_name'} />
|
||||
</strong>
|
||||
}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
className={'form-group--name'}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name="role_name" />}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
medium={true}
|
||||
inputRef={(ref) => (roleNameFieldRef.current = ref)}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
{/* ---------- description ---------- */}
|
||||
<FastField name={'role_description'}>
|
||||
{({ field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={<T id={'description'} />}
|
||||
className={'form-group--description'}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name={'role_description'} />}
|
||||
inline={true}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
height={280}
|
||||
{...field}
|
||||
placeholder="Max. 500 characters"
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { FormikObserver } from 'components';
|
||||
|
||||
/**
|
||||
* Role form observer.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
export function RoleFormObserver() {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
// Handles the form change.
|
||||
const handleFormChange = () => {};
|
||||
|
||||
return <FormikObserver onChange={handleFormChange} values={values} />;
|
||||
}
|
||||
@@ -3,32 +3,28 @@ import { useHistory } from 'react-router-dom';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Formik } from 'formik';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
|
||||
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 withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import RolesFormContent from './RolesFormContent';
|
||||
import {
|
||||
getNewRoleInitialValues,
|
||||
transformToArray,
|
||||
transformToObject,
|
||||
} from './utils';
|
||||
|
||||
import RolesFormContent from './RolesFormContent';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import { handleDeleteErrors } from '../utils';
|
||||
|
||||
import { compose, transformToForm } from 'utils';
|
||||
|
||||
const defaultValues = {
|
||||
role_name: '',
|
||||
role_description: '',
|
||||
permissions: {},
|
||||
serviceFullAccess: {},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,67 +1,14 @@
|
||||
import React from 'react';
|
||||
import { ErrorMessage, FastField, Form } from 'formik';
|
||||
import { FormGroup, InputGroup, TextArea } from '@blueprintjs/core';
|
||||
import { Form } from 'formik';
|
||||
|
||||
import { inputIntent } from 'utils';
|
||||
import { FormattedMessage as T, FieldRequiredHint, Card } from 'components';
|
||||
import { useAutofocus } from 'hooks';
|
||||
import { RoleFormHeader } from './RoleFormHeader';
|
||||
import { RolesPermissionList } from './components';
|
||||
import { RoleFormFloatingActions } from './RoleFormFloatingActions';
|
||||
|
||||
/**
|
||||
* Role form header.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function RoleFormHeader() {
|
||||
const roleNameFieldRef = useAutofocus();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{/* ---------- name ---------- */}
|
||||
<FastField name={'role_name'}>
|
||||
{({ field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={<strong><T id={'role_name'} /></strong>}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
className={'form-group--name'}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name="role_name" />}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
medium={true}
|
||||
inputRef={(ref) => (roleNameFieldRef.current = ref)}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
{/* ---------- description ---------- */}
|
||||
<FastField name={'role_description'}>
|
||||
{({ field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={<T id={'description'} />}
|
||||
className={'form-group--description'}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name={'role_description'} />}
|
||||
inline={true}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
height={280}
|
||||
{...field}
|
||||
placeholder="Max. 500 characters"
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
import { RoleFormObserver } from './RoleFormObserver';
|
||||
|
||||
/**
|
||||
* Preferences - Roles Form content.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
export default function RolesFormContent() {
|
||||
return (
|
||||
@@ -69,6 +16,7 @@ export default function RolesFormContent() {
|
||||
<RoleFormHeader />
|
||||
<RolesPermissionList />
|
||||
<RoleFormFloatingActions />
|
||||
<RoleFormObserver />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import React from 'react';
|
||||
import { Checkbox, Switch, Popover } from '@blueprintjs/core';
|
||||
import { Checkbox, Popover } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
import { FastField } from 'formik';
|
||||
import { Field } from 'formik';
|
||||
|
||||
import { permissions, ModulePermissionsStyle } from 'common/permissionsSchema';
|
||||
import { Card, If, ButtonLink, Choose } from 'components';
|
||||
import {
|
||||
getSerivceColumnPermission,
|
||||
getServiceExtraPermissions,
|
||||
FULL_ACCESS_CHECKBOX_STATE,
|
||||
handleCheckboxFullAccessChange,
|
||||
handleCheckboxPermissionChange,
|
||||
} from './utils';
|
||||
|
||||
// Module permissions context.
|
||||
@@ -66,15 +69,20 @@ function PermissionBodyColumn({ column }) {
|
||||
if (!permission) {
|
||||
return <td class={'permission-checkbox'}></td>;
|
||||
}
|
||||
|
||||
return (
|
||||
<td class={'permission-checkbox'}>
|
||||
<FastField
|
||||
<Field
|
||||
name={`permissions.${service.subject}/${permission.key}`}
|
||||
type="checkbox"
|
||||
>
|
||||
{({ field }) => <PermissionCheckbox inline={true} {...field} />}
|
||||
</FastField>
|
||||
{({ field, form }) => (
|
||||
<PermissionCheckbox
|
||||
inline={true}
|
||||
{...field}
|
||||
onChange={handleCheckboxPermissionChange(form, permission, service)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -103,18 +111,23 @@ function ModuleExtraPermissionsPopover() {
|
||||
|
||||
<ExtraPermissionsRoot>
|
||||
{extraPermissions.map((permission) => (
|
||||
<FastField
|
||||
<Field
|
||||
name={`permissions.${service.subject}/${permission.key}`}
|
||||
type="checkbox"
|
||||
>
|
||||
{({ form: { setFieldValue, values }, field }) => (
|
||||
{({ form, field }) => (
|
||||
<PermissionCheckbox
|
||||
inline={true}
|
||||
label={permission.label}
|
||||
{...field}
|
||||
onChange={handleCheckboxPermissionChange(
|
||||
form,
|
||||
permission,
|
||||
service,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
</Field>
|
||||
))}
|
||||
</ExtraPermissionsRoot>
|
||||
</Popover>
|
||||
@@ -179,14 +192,18 @@ function ModulePermissionsServiceFullAccess() {
|
||||
return (
|
||||
<If condition={module.serviceFullAccess}>
|
||||
<td class="full-access-permission">
|
||||
<FastField
|
||||
name={`serviceFullAccess.${service.subject}`}
|
||||
type="checkbox"
|
||||
>
|
||||
{({ form: { setFieldValue, values }, field }) => (
|
||||
<PermissionCheckbox inline={true} />
|
||||
<Field name={`serviceFullAccess.${service.subject}`} type="checkbox">
|
||||
{({ form, field }) => (
|
||||
<PermissionCheckbox
|
||||
inline={true}
|
||||
{...field}
|
||||
indeterminate={
|
||||
field.value === FULL_ACCESS_CHECKBOX_STATE.INDETARMINE
|
||||
}
|
||||
onChange={handleCheckboxFullAccessChange(service, form)}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
</Field>
|
||||
</td>
|
||||
</If>
|
||||
);
|
||||
@@ -242,18 +259,23 @@ function ModuleVerticalTableCells() {
|
||||
<td class={'permissions'}>
|
||||
{service.permissions.map((permission) => (
|
||||
<div>
|
||||
<FastField
|
||||
<Field
|
||||
name={`permissions.${service.subject}/${permission.key}`}
|
||||
type="checkbox"
|
||||
>
|
||||
{({ form: { setFieldValue, values }, field }) => (
|
||||
{({ form, field }) => (
|
||||
<PermissionCheckbox
|
||||
inline={true}
|
||||
label={permission.label}
|
||||
{...field}
|
||||
onChange={handleCheckboxPermissionChange(
|
||||
form,
|
||||
permission,
|
||||
service,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
</Field>
|
||||
</div>
|
||||
))}
|
||||
</td>
|
||||
@@ -319,12 +341,6 @@ function ModulePermissions({ module }) {
|
||||
<ModulePermissionsProvider module={module}>
|
||||
<ModulePermissionHead>
|
||||
<ModulePermissionTitle>{module.label} </ModulePermissionTitle>
|
||||
|
||||
<If condition={module.moduleFullAccess}>
|
||||
<ModulePermissionFullControlRoot>
|
||||
<Switch />
|
||||
</ModulePermissionFullControlRoot>
|
||||
</If>
|
||||
</ModulePermissionHead>
|
||||
|
||||
<ModulePermissionsBody />
|
||||
@@ -380,15 +396,6 @@ const ModulePermissionTitle = styled.div`
|
||||
color: #878787;
|
||||
`;
|
||||
|
||||
const ModulePermissionFullControlRoot = styled.div`
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
|
||||
.bp3-switch {
|
||||
margin: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const ModulePermissionBodyRoot = styled.div``;
|
||||
|
||||
const ModulePermissionsTableRoot = styled.table`
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
import { chain } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
getPermissionsSchemaService,
|
||||
getPermissionsSchemaServices,
|
||||
} from 'common/permissionsSchema';
|
||||
|
||||
export const FULL_ACCESS_CHECKBOX_STATE = {
|
||||
INDETARMINE: -1,
|
||||
ON: true,
|
||||
OFF: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the permissions object to array.
|
||||
* @returns
|
||||
*/
|
||||
export const transformToArray = ({ permissions }) => {
|
||||
return Object.keys(permissions).map((index) => {
|
||||
const [value, key] = index.split('/');
|
||||
@@ -10,6 +27,20 @@ export const transformToArray = ({ permissions }) => {
|
||||
});
|
||||
};
|
||||
|
||||
function transformPermissions(permissions) {
|
||||
return Object.keys(permissions).map((permissionKey) => {
|
||||
const [subject, key] = permissionKey.split('/');
|
||||
const value = permissions[permissionKey];
|
||||
|
||||
return { key, subject, value };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes permissions array to object.
|
||||
* @param {*} permissions -
|
||||
* @returns
|
||||
*/
|
||||
export const transformPermissionsToObject = (permissions) => {
|
||||
const output = {};
|
||||
permissions.forEach((item) => {
|
||||
@@ -18,6 +49,11 @@ export const transformPermissionsToObject = (permissions) => {
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} role
|
||||
* @returns
|
||||
*/
|
||||
export const transformToObject = (role) => {
|
||||
return {
|
||||
role_name: role.name,
|
||||
@@ -44,22 +80,162 @@ export const getDefaultValuesFromSchema = (schema) => {
|
||||
.flat();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve initial values of full access services.
|
||||
* @param {*} formPermissions
|
||||
* @returns
|
||||
*/
|
||||
export const getInitialServicesFullAccess = (formPermissions) => {
|
||||
const services = getPermissionsSchemaServices();
|
||||
|
||||
return chain(services)
|
||||
.map((service) => {
|
||||
const { subject } = service;
|
||||
const isFullChecked = isServiceFullChecked(subject, formPermissions);
|
||||
const isFullUnchecked = isServiceFullUnchecked(subject, formPermissions);
|
||||
const value = detarmineCheckboxState(isFullChecked, isFullUnchecked);
|
||||
|
||||
return [service.subject, value];
|
||||
})
|
||||
.fromPairs()
|
||||
.value();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} schema
|
||||
* @returns
|
||||
*/
|
||||
export const getNewRoleInitialValues = (schema) => {
|
||||
const permissions = transformPermissionsToObject(
|
||||
getDefaultValuesFromSchema(schema),
|
||||
);
|
||||
const serviceFullAccess = getInitialServicesFullAccess(permissions);
|
||||
|
||||
return {
|
||||
permissions: transformPermissionsToObject(
|
||||
getDefaultValuesFromSchema(schema),
|
||||
),
|
||||
permissions,
|
||||
serviceFullAccess,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} service
|
||||
* @param {*} columnKey
|
||||
* @returns
|
||||
*/
|
||||
export function getSerivceColumnPermission(service, columnKey) {
|
||||
return service.permissions.find((permission) => {
|
||||
return permission.relatedColumn === columnKey;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} service
|
||||
* @returns
|
||||
*/
|
||||
export function getServiceExtraPermissions(service) {
|
||||
return service.permissions.filter((permission) => {
|
||||
return !permission.relatedColumn;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the given service subject is full permissions checked.
|
||||
*/
|
||||
export function isServiceFullChecked(subject, permissions) {
|
||||
const serviceSchema = getPermissionsSchemaService(subject);
|
||||
|
||||
return serviceSchema.permissions.every(
|
||||
(permission) => permissions[`${subject}/${permission.key}`],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the given service subject is fully associated permissions unchecked.
|
||||
* @param {string} subject -
|
||||
* @param {Object} permissionsMap -
|
||||
*/
|
||||
export function isServiceFullUnchecked(subject, permissionsMap) {
|
||||
const serviceSchema = getPermissionsSchemaService(subject);
|
||||
|
||||
return serviceSchema.permissions.every(
|
||||
(permission) => !permissionsMap[`${subject}/${permission.key}`],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles permission checkbox change.
|
||||
*/
|
||||
export const handleCheckboxPermissionChange = R.curry(
|
||||
(form, permission, service, event) => {
|
||||
const { subject } = service;
|
||||
const isChecked = event.currentTarget.checked;
|
||||
const newValues = {
|
||||
...form.values,
|
||||
permissions: {
|
||||
...form.values.permissions,
|
||||
[`${subject}/${permission.key}`]: isChecked,
|
||||
},
|
||||
};
|
||||
const isFullChecked = isServiceFullChecked(subject, newValues.permissions);
|
||||
const isFullUnchecked = isServiceFullUnchecked(
|
||||
subject,
|
||||
newValues.permissions,
|
||||
);
|
||||
form.setFieldValue(`permissions.${subject}/${permission.key}`, isChecked);
|
||||
form.setFieldValue(
|
||||
`serviceFullAccess.${subject}`,
|
||||
detarmineCheckboxState(isFullChecked, isFullUnchecked),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Detarmines the permission checkbox state.
|
||||
* @param {boolean} isFullChecked
|
||||
* @param {boolean} isFullUnchecked
|
||||
* @returns {FULL_ACCESS_CHECKBOX_STATE}
|
||||
*/
|
||||
function detarmineCheckboxState(isFullChecked, isFullUnchecked) {
|
||||
return isFullChecked
|
||||
? FULL_ACCESS_CHECKBOX_STATE.ON
|
||||
: isFullUnchecked
|
||||
? FULL_ACCESS_CHECKBOX_STATE.OFF
|
||||
: FULL_ACCESS_CHECKBOX_STATE.INDETARMINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreive the service all permissions paths.
|
||||
* @param {string} subject
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function getServiceAllPermissionsPaths(subject) {
|
||||
const service = getPermissionsSchemaService(subject);
|
||||
|
||||
return service.permissions.map(
|
||||
(perm) => `permissions.${subject}/${perm.key}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle full access service checkbox change.
|
||||
*/
|
||||
export const handleCheckboxFullAccessChange = R.curry(
|
||||
(service, form, event) => {
|
||||
const isChecked = event.currentTarget.checked;
|
||||
const permsPaths = getServiceAllPermissionsPaths(service.subject);
|
||||
|
||||
form.setFieldValue(`serviceFullAccess.${service.subject}`, isChecked);
|
||||
|
||||
permsPaths.forEach((permissionPath) => {
|
||||
form.setFieldValue(
|
||||
permissionPath,
|
||||
isChecked
|
||||
? FULL_ACCESS_CHECKBOX_STATE.ON
|
||||
: FULL_ACCESS_CHECKBOX_STATE.OFF,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user