✨ Roles with rules working
This commit is contained in:
parent
1a7e5bffb6
commit
ffd736a4f7
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
49
src/front/Api/LeCoffreApi/Admin/Rules/Rules.ts
Normal file
49
src/front/Api/LeCoffreApi/Admin/Rules/Rules.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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],
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
<div className={classes["user-infos-row"]}>
|
<div className={classes["rights-container"]}>
|
||||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
<div className={classes["rights-header"]}>
|
||||||
Prénom
|
<Typography typo={ITypo.P_SB_18}>Modifier les droits</Typography>
|
||||||
</Typography>
|
|
||||||
<Typography typo={ITypo.P_18}>{userSelected?.contact?.last_name}</Typography>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={classes["user-infos-row"]}>
|
<div className={classes["select-all-container"]}>
|
||||||
<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 className={classes["role-container"]}>
|
|
||||||
<div className={classes["first-line"]}>
|
|
||||||
<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 className={classes["second-line"]}>
|
|
||||||
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
5
src/pages/roles/[roleUid]/index.tsx
Normal file
5
src/pages/roles/[roleUid]/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import RolesInformations from "@Front/Components/Layouts/Roles/RolesInformations";
|
||||||
|
|
||||||
|
export default function Route() {
|
||||||
|
return <RolesInformations />;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user