Roles with rules working

This commit is contained in:
Maxime Lalo 2023-07-17 11:19:44 +02:00
parent 1a7e5bffb6
commit ffd736a4f7
8 changed files with 223 additions and 133 deletions

View File

@ -2,21 +2,32 @@ import { Role } from "le-coffre-resources/dist/Admin";
import BaseAdmin from "../BaseAdmin"; import BaseAdmin from "../BaseAdmin";
// TODO Type get query params -> Where + inclue + orderby export type IGetRolesParams = {
export interface IGetRolesParams {
where?: {}; where?: {};
include?: {}; include?: {};
select?: {}; select?: {};
} };
export type IPutRoleParams = {
rules: Role["rules"];
};
export default class Roles extends BaseAdmin { export default class Roles extends BaseAdmin {
public static instance: Roles = new this(); private static instance: Roles;
private readonly baseURl = this.namespaceUrl.concat("/roles"); private readonly baseURl = this.namespaceUrl.concat("/roles");
private constructor() { private constructor() {
super(); super();
} }
public static getInstance() {
if (!this.instance) {
return new Roles();
} else {
return this.instance;
}
}
public async get(q: IGetRolesParams): Promise<Role[]> { public async get(q: IGetRolesParams): Promise<Role[]> {
const url = new URL(this.baseURl); const url = new URL(this.baseURl);
const query = { q }; const query = { q };
@ -39,4 +50,14 @@ export default class Roles extends BaseAdmin {
return Promise.reject(err); return Promise.reject(err);
} }
} }
public async put(uid: string, body: IPutRoleParams): Promise<Role> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest<Role>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
} }

View File

@ -0,0 +1,49 @@
import { Rule } from "le-coffre-resources/dist/Admin";
import BaseAdmin from "../BaseAdmin";
export type IGetRulesParams = {
where?: {};
include?: {};
select?: {};
};
export default class Rules extends BaseAdmin {
private static instance: Rules;
private readonly baseURl = this.namespaceUrl.concat("/rules");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new Rules();
} else {
return this.instance;
}
}
public async get(q: IGetRulesParams): Promise<Rule[]> {
const url = new URL(this.baseURl);
const query = { q };
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Rule[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<Rule> {
const url = new URL(this.baseURl.concat(`/${uid}`));
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Rule>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -9,13 +9,29 @@ type IProps = {
name?: string; name?: string;
option: IOption; option: IOption;
toolTip?: string; toolTip?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
checked: boolean;
}; };
export default class CheckBox extends React.Component<IProps> { type IState = {
checked: boolean;
};
export default class CheckBox extends React.Component<IProps, IState> {
static defaultProps = { static defaultProps = {
toolTip: "", toolTip: "",
checked: false,
}; };
constructor(props: IProps) {
super(props);
this.state = {
checked: this.props.checked ?? false,
};
this.onChange = this.onChange.bind(this);
}
public override render(): JSX.Element { public override render(): JSX.Element {
return ( return (
<Typography typo={ITypo.P_ERR_16} color={ITypoColor.BLACK}> <Typography typo={ITypo.P_ERR_16} color={ITypoColor.BLACK}>
@ -24,6 +40,8 @@ export default class CheckBox extends React.Component<IProps> {
type="checkbox" type="checkbox"
name={this.props.name ?? (this.props.option.value as string)} name={this.props.name ?? (this.props.option.value as string)}
value={this.props.option.value as string} value={this.props.option.value as string}
onChange={this.onChange}
checked={this.state.checked}
/> />
{this.props.option.label} {this.props.option.label}
{this.props.toolTip && <Tooltip className={classes["tooltip"]} text={this.props.toolTip} />} {this.props.toolTip && <Tooltip className={classes["tooltip"]} text={this.props.toolTip} />}
@ -31,4 +49,20 @@ export default class CheckBox extends React.Component<IProps> {
</Typography> </Typography>
); );
} }
public override componentDidUpdate(prevProps: Readonly<IProps>): void {
if (prevProps.checked !== this.props.checked) {
this.setState({
checked: this.props.checked,
});
}
}
private onChange(e: React.ChangeEvent<HTMLInputElement>) {
this.setState({
checked: !this.state.checked,
});
this.props.onChange && this.props.onChange(e);
}
} }

View File

@ -20,9 +20,7 @@ export default function RoleListContainer(props: IProps) {
const filterRoles = useCallback( const filterRoles = useCallback(
(input: string) => { (input: string) => {
const filteredUsers = props.roles.filter((role) => { const filteredUsers = props.roles.filter((role) => {
return ( return role.name?.toLowerCase().includes(input.toLowerCase());
role.name?.toLowerCase().includes(input.toLowerCase())
);
}); });
setFilteredUsers(filteredUsers); setFilteredUsers(filteredUsers);
}, },
@ -32,7 +30,7 @@ export default function RoleListContainer(props: IProps) {
const onSelectedBlock = useCallback( const onSelectedBlock = useCallback(
(block: IBlock) => { (block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide(); props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.Collaborators.pages.CollaboratorInformations.props.path; const redirectPath = Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id)); router.push(redirectPath.replace("[uid]", block.id));
}, },
[props, router], [props, router],

View File

@ -85,27 +85,14 @@ export default class DefaultRoleDashboard extends React.Component<IProps, IState
public override async componentDidMount() { public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window)); this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
// const query: IGetRolesParams = { const query: IGetRolesParams = {
// include: { rules: true }, include: { rules: true },
// }; };
// const roles = await Roles.instance.get(query); const roles = await Roles.getInstance().get(query);
const roles: Role[] = [
Role.hydrate<Role>({
name: "Notaire",
uid: "1",
}),
Role.hydrate<Role>({
name: "Clerc de notaire",
uid: "2",
}),
Role.hydrate<Role>({
name: "Collaborateur",
uid: "3",
}),
];
this.setState({ roles }); this.setState({ roles });
} }
public override componentWillUnmount() { public override componentWillUnmount() {
this.onWindowResize(); this.onWindowResize();
} }

View File

@ -1,45 +1,26 @@
@import "@Themes/constants.scss"; @import "@Themes/constants.scss";
.root { .root {
.user-infos { .subtitle {
background-color: var(--grey-soft);
display: flex;
justify-content: space-between;
padding: 24px;
margin-top: 32px; margin-top: 32px;
}
@media (max-width: $screen-l) { .rights-container {
margin-top: 32px;
padding: 32px 16px;
border: 1px solid gray;
.select-all-container {
margin-top: 32px;
}
.rights {
margin-top: 32px;
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 32px; gap: 32px;
} }
@media (max-width: $screen-s) { .save-container {
grid-template-columns: repeat(1, 1fr);
}
.user-infos-row {
display: flex;
flex-direction: column;
gap: 12px;
}
}
.role-container {
padding: 32px 16px;
border: 1px solid var(--grey);
margin-top: 32px;
.first-line {
display: flex;
justify-content: space-between;
}
.second-line {
margin-top: 32px;
}
.third-line {
margin-top: 32px; margin-top: 32px;
} }
} }

View File

@ -1,100 +1,115 @@
import Users from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; import Roles from "@Front/Api/LeCoffreApi/Admin/Roles/Roles";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import Rules from "@Front/Api/LeCoffreApi/Admin/Rules/Rules";
import DefaultCollaboratorDashboard from "@Front/Components/LayoutTemplates/DefaultCollaboratorDashboard"; import Button from "@Front/Components/DesignSystem/Button";
import User from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import classes from "./classes.module.scss";
import Link from "next/link";
import SelectField from "@Front/Components/DesignSystem/Form/SelectField";
import CheckBox from "@Front/Components/DesignSystem/CheckBox"; import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Form from "@Front/Components/DesignSystem/Form";
import ChevronIcon from "@Assets/Icons/chevron.svg"; import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
type IProps = {}; import DefaultRoleDashboard from "@Front/Components/LayoutTemplates/DefaultRoleDashboard";
export default function CollaboratorInformations(props: IProps) { import { Role, Rule } from "le-coffre-resources/dist/Admin";
const router = useRouter(); import { useRouter } from "next/router";
let { collaboratorUid } = router.query; import { useCallback, useEffect, useState } from "react";
const [userSelected, setUserSelected] = useState<User | null>(null); import classes from "./classes.module.scss";
type IProps = {};
type RuleCheckbox = Rule & {
checked: boolean;
};
export default function RolesInformations(props: IProps) {
const router = useRouter();
let { roleUid } = router.query;
const [roleSelected, setRoleSelected] = useState<Role | null>(null);
const [rulesCheckboxes, setRulesCheckboxes] = useState<RuleCheckbox[]>([]);
useEffect(() => { useEffect(() => {
async function getUser() { async function getUser() {
if (!collaboratorUid) return; if (!roleUid) return;
const user = await Users.getInstance().getByUid(collaboratorUid as string, { const role = await Roles.getInstance().getByUid(roleUid as string, {
q: { q: {
contact: true, rules: true,
}, },
}); });
if (!user) return;
setUserSelected(user); const rules = await Rules.getInstance().get({});
if (!role) return;
setRoleSelected(role);
if (!role.rules) return;
const rulesCheckboxes = rules
.map((rule) => {
if (role.rules?.find((r) => r.uid === rule.uid)) {
return { ...rule, checked: true };
}
return { ...rule, checked: false };
})
.sort((rule) => (rule.checked ? -1 : 1));
setRulesCheckboxes(rulesCheckboxes);
} }
getUser(); getUser();
}, [collaboratorUid]); }, [roleUid]);
const handleSelectAllChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const checked = e.target.checked;
rulesCheckboxes.forEach((rule) => (rule.checked = checked));
setRulesCheckboxes([...rulesCheckboxes]);
},
[rulesCheckboxes],
);
const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
if (!roleSelected || !roleSelected.uid) return;
const rules = rulesCheckboxes.filter((rule) => rule.checked)?.map((rule) => Rule.hydrate<Rule>(rule));
const role = await Roles.getInstance().put(roleSelected.uid, {
rules,
});
if (!role) return;
setRoleSelected(role);
if (!role.rules) return;
setRulesCheckboxes(role.rules.map((rule) => ({ ...rule, checked: false })));
},
[roleSelected, rulesCheckboxes],
);
const mockedRole = { value: "1", label: "Clerc de notaire" };
return ( return (
<DefaultCollaboratorDashboard mobileBackText={"Liste des collaborateurs"}> <DefaultRoleDashboard mobileBackText={"Liste des rôles"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["folder-header"]}> <div className={classes["header"]}>
<Typography typo={ITypo.H1Bis}>{userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name}</Typography> <Typography typo={ITypo.H1Bis}>Gestion des rôles</Typography>
</div> </div>
<div className={classes["user-infos"]}> <div className={classes["subtitle"]}>
<div className={classes["user-infos-row"]}> <Typography typo={ITypo.H3}>{roleSelected?.name}</Typography>
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
Nom
</Typography>
<Typography typo={ITypo.P_18}>{userSelected?.contact?.first_name}</Typography>
</div>
<div className={classes["user-infos-row"]}>
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
Prénom
</Typography>
<Typography typo={ITypo.P_18}>{userSelected?.contact?.last_name}</Typography>
</div>
<div className={classes["user-infos-row"]}>
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
Numéro de téléphone
</Typography>
<Typography typo={ITypo.P_18}>{userSelected?.contact?.phone_number}</Typography>
</div>
<div className={classes["user-infos-row"]}>
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
Email
</Typography>
<Typography typo={ITypo.P_18}>{userSelected?.contact?.email}</Typography>
</div>
</div> </div>
<div className={classes["rights-container"]}>
<div className={classes["role-container"]}> <div className={classes["rights-header"]}>
<div className={classes["first-line"]}> <Typography typo={ITypo.P_SB_18}>Modifier les droits</Typography>
<Typography typo={ITypo.P_SB_18}>Modifier le rôle</Typography>
<div className={classes["gestion-role"]}>
<Link href={`/collaborators/${collaboratorUid}/role`}>
<Button
icon={ChevronIcon}
iconposition={"right"}
iconstyle={{ width: "22px", height: "22px" }}
variant={EButtonVariant.LINE}>
Gestion des rôles
</Button>
</Link>
</div>
</div> </div>
<div className={classes["second-line"]}> <div className={classes["select-all-container"]}>
<SelectField placeholder="Rôle" name="role" options={[mockedRole]} selectedOption={mockedRole} />
</div>
<div className={classes["third-line"]}>
<CheckBox <CheckBox
option={{ option={{
value: "1", label: "Tout sélectionner",
label: "Nommer administrateur de l'office", value: "all",
}} }}
toolTip="blabla" toolTip="Tout sélectionner"
onChange={handleSelectAllChange}
/> />
</div> </div>
<Form onSubmit={onSubmitHandler}>
<div className={classes["rights"]}>
{rulesCheckboxes.map((rule) => (
<div className={classes["right"]} key={rule.uid}>
<CheckBox option={{ label: rule.name, value: rule.uid }} checked={rule.checked} />
</div>
))}
</div>
<div className={classes["save-container"]}>
<Button type="submit">Enregistrer</Button>
</div>
</Form>
</div> </div>
</div> </div>
</DefaultCollaboratorDashboard> </DefaultRoleDashboard>
); );
} }

View File

@ -0,0 +1,5 @@
import RolesInformations from "@Front/Components/Layouts/Roles/RolesInformations";
export default function Route() {
return <RolesInformations />;
}