This commit is contained in:
Maxime Lalo 2023-07-16 17:19:52 +02:00
parent 37989122b9
commit 1a7e5bffb6
16 changed files with 642 additions and 6 deletions

View File

@ -0,0 +1,5 @@
import BaseApiService from "@Front/Api/BaseApiService";
export default abstract class BaseAdmin extends BaseApiService {
protected readonly namespaceUrl = this.getBaseUrl().concat("/admin");
}

View File

@ -0,0 +1,42 @@
import { Role } from "le-coffre-resources/dist/Admin";
import BaseAdmin from "../BaseAdmin";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetRolesParams {
where?: {};
include?: {};
select?: {};
}
export default class Roles extends BaseAdmin {
public static instance: Roles = new this();
private readonly baseURl = this.namespaceUrl.concat("/roles");
private constructor() {
super();
}
public async get(q: IGetRolesParams): Promise<Role[]> {
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<Role[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<Role> {
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<Role>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,21 @@
@import "@Themes/constants.scss";
.root {
width: calc(100vh - 83px);
display: flex;
flex-direction: column;
justify-content: space-between;
.header {
flex: 1;
}
.searchbar {
padding: 40px 24px 24px 24px;
}
.folderlist-container {
height: 100%;
border-right: 1px solid var(--grey-medium);
}
}

View File

@ -0,0 +1,61 @@
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import SearchBar from "@Front/Components/DesignSystem/SearchBar";
import Module from "@Front/Config/Module";
import { Role } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router";
import React, { useCallback, useState } from "react";
import classes from "./classes.module.scss";
type IProps = {
roles: Role[];
onSelectedRole?: (role: Role) => void;
onCloseLeftSide?: () => void;
};
export default function RoleListContainer(props: IProps) {
const [filteredUsers, setFilteredUsers] = useState<Role[]>(props.roles);
const router = useRouter();
const filterRoles = useCallback(
(input: string) => {
const filteredUsers = props.roles.filter((role) => {
return (
role.name?.toLowerCase().includes(input.toLowerCase())
);
});
setFilteredUsers(filteredUsers);
},
[props.roles],
);
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.Collaborators.pages.CollaboratorInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],
);
return (
<div className={classes["root"]}>
<div className={classes["header"]}>
<div className={classes["searchbar"]}>
<SearchBar onChange={filterRoles} placeholder="Chercher un collaborateur" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList
blocks={filteredUsers.map((role) => {
return {
name: role.name,
id: role.uid!,
};
})}
onSelectedBlock={onSelectedBlock}
/>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,117 @@
@import "@Themes/constants.scss";
@keyframes growWidth {
0% {
width: 100%;
}
100% {
width: 200%;
}
}
.root {
.content {
display: flex;
overflow: hidden;
height: calc(100vh - 83px);
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--white);
opacity: 0.5;
z-index: 2;
transition: all 0.3s $custom-easing;
}
.left-side {
background-color: $white;
z-index: 3;
display: flex;
width: 389px;
min-width: 389px;
transition: all 0.3s $custom-easing;
overflow: hidden;
@media (max-width: ($screen-m - 1px)) {
width: 56px;
min-width: 56px;
transform: translateX(-389px);
&.opened {
transform: translateX(0px);
width: 389px;
min-width: 389px;
}
}
@media (max-width: $screen-s) {
width: 0px;
min-width: 0px;
&.opened {
width: 100vw;
min-width: 100vw;
}
}
}
.closable-left-side {
position: absolute;
background-color: $white;
z-index: 0;
display: flex;
justify-content: center;
min-width: 56px;
max-width: 56px;
height: calc(100vh - 83px);
border-right: 1px $grey-medium solid;
@media (min-width: $screen-m) {
display: none;
}
.chevron-icon {
margin-top: 21px;
transform: rotate(180deg);
cursor: pointer;
}
@media (max-width: $screen-s) {
display: none;
}
}
.right-side {
min-width: calc(100vw - 389px);
padding: 64px 48px;
overflow-y: auto;
@media (max-width: ($screen-m - 1px)) {
min-width: calc(100vw - 56px);
}
@media (max-width: $screen-s) {
padding: 40px 16px 64px 16px;
flex: 1;
min-width: unset;
}
.back-arrow-mobile {
display: none;
@media (max-width: $screen-s) {
display: block;
margin-bottom: 24px;
}
}
.back-arrow-desktop {
@media (max-width: $screen-s) {
display: none;
}
}
}
}
}

View File

@ -0,0 +1,129 @@
import ChevronIcon from "@Assets/Icons/chevron.svg";
import Roles, { IGetRolesParams } from "@Front/Api/LeCoffreApi/Admin/Roles/Roles";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow";
import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames";
import { Role } from "le-coffre-resources/dist/Admin";
import Image from "next/image";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss";
import RoleListContainer from "./RoleListContainer";
type IProps = {
title: string;
children?: ReactNode;
onSelectedRole: (role: Role) => void;
hasBackArrow: boolean;
backArrowUrl?: string;
mobileBackText?: string;
};
type IState = {
roles: Role[] | null;
isLeftSideOpen: boolean;
leftSideCanBeClosed: boolean;
};
export default class DefaultRoleDashboard extends React.Component<IProps, IState> {
private onWindowResize = () => {};
public static defaultProps: Partial<IProps> = {
hasBackArrow: false,
};
public constructor(props: IProps) {
super(props);
this.state = {
roles: null,
isLeftSideOpen: false,
leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false,
};
this.onOpenLeftSide = this.onOpenLeftSide.bind(this);
this.onCloseLeftSide = this.onCloseLeftSide.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />}
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}>
{this.state.roles && <RoleListContainer roles={this.state.roles} onCloseLeftSide={this.onCloseLeftSide} />}
</div>
<div className={classNames(classes["closable-left-side"])}>
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} />
</div>
<div className={classes["right-side"]}>
{this.props.hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
icon={ChevronIcon}
iconposition={"left"}
iconstyle={{ transform: "rotate(180deg)", width: "22px", height: "22px" }}
variant={EButtonVariant.LINE}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
</div>
<Version />
</div>
);
}
public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
// const query: IGetRolesParams = {
// include: { rules: true },
// };
// const roles = await Roles.instance.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 });
}
public override componentWillUnmount() {
this.onWindowResize();
}
private onOpenLeftSide() {
this.setState({ isLeftSideOpen: true });
}
private onCloseLeftSide() {
if (!this.state.leftSideCanBeClosed) return;
this.setState({ isLeftSideOpen: false });
}
private onResize(window: Window) {
if (window.innerWidth > 1023) {
if (!this.state.leftSideCanBeClosed) return;
this.setState({ leftSideCanBeClosed: false });
}
this.setState({ leftSideCanBeClosed: true });
}
}

View File

@ -1,15 +1,18 @@
import ChevronIcon from "@Assets/Icons/chevron.svg";
import Users from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; import Users from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import SelectField from "@Front/Components/DesignSystem/Form/SelectField";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultCollaboratorDashboard from "@Front/Components/LayoutTemplates/DefaultCollaboratorDashboard"; import DefaultCollaboratorDashboard from "@Front/Components/LayoutTemplates/DefaultCollaboratorDashboard";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Link from "next/link"; import Module from "@Front/Config/Module";
import SelectField from "@Front/Components/DesignSystem/Form/SelectField";
import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import ChevronIcon from "@Assets/Icons/chevron.svg";
type IProps = {}; type IProps = {};
export default function CollaboratorInformations(props: IProps) { export default function CollaboratorInformations(props: IProps) {
const router = useRouter(); const router = useRouter();
@ -70,7 +73,7 @@ export default function CollaboratorInformations(props: IProps) {
<div className={classes["first-line"]}> <div className={classes["first-line"]}>
<Typography typo={ITypo.P_SB_18}>Modifier le rôle</Typography> <Typography typo={ITypo.P_SB_18}>Modifier le rôle</Typography>
<div className={classes["gestion-role"]}> <div className={classes["gestion-role"]}>
<Link href={`/collaborators/${collaboratorUid}/role`}> <Link href={Module.getInstance().get().modules.pages.Roles.props.path}>
<Button <Button
icon={ChevronIcon} icon={ChevronIcon}
iconposition={"right"} iconposition={"right"}

View File

@ -0,0 +1,46 @@
@import "@Themes/constants.scss";
.root {
.user-infos {
background-color: var(--grey-soft);
display: flex;
justify-content: space-between;
padding: 24px;
margin-top: 32px;
@media (max-width: $screen-l) {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32px;
}
@media (max-width: $screen-s) {
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;
}
}
}

View File

@ -0,0 +1,100 @@
import Users from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultCollaboratorDashboard from "@Front/Components/LayoutTemplates/DefaultCollaboratorDashboard";
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 Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import ChevronIcon from "@Assets/Icons/chevron.svg";
type IProps = {};
export default function CollaboratorInformations(props: IProps) {
const router = useRouter();
let { collaboratorUid } = router.query;
const [userSelected, setUserSelected] = useState<User | null>(null);
useEffect(() => {
async function getUser() {
if (!collaboratorUid) return;
const user = await Users.getInstance().getByUid(collaboratorUid as string, {
q: {
contact: true,
},
});
if (!user) return;
setUserSelected(user);
}
getUser();
}, [collaboratorUid]);
const mockedRole = { value: "1", label: "Clerc de notaire" };
return (
<DefaultCollaboratorDashboard mobileBackText={"Liste des collaborateurs"}>
<div className={classes["root"]}>
<div className={classes["folder-header"]}>
<Typography typo={ITypo.H1Bis}>{userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name}</Typography>
</div>
<div className={classes["user-infos"]}>
<div className={classes["user-infos-row"]}>
<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 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
option={{
value: "1",
label: "Nommer administrateur de l'office",
}}
toolTip="blabla"
/>
</div>
</div>
</div>
</DefaultCollaboratorDashboard>
);
}

View File

@ -0,0 +1,17 @@
@import "@Themes/constants.scss";
.root {
display: flex;
align-items: center;
flex-direction: column;
min-height: 100%;
.no-role-selected {
width: 100%;
.choose-a-role {
margin-top: 96px;
text-align: center;
}
}
}

View File

@ -0,0 +1,26 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultRoleDashboard from "@Front/Components/LayoutTemplates/DefaultRoleDashboard";
import BasePage from "../Base";
import classes from "./classes.module.scss";
type IProps = {};
type IState = {};
export default class Collaborators extends BasePage<IProps, IState> {
public override render(): JSX.Element {
return (
<DefaultRoleDashboard mobileBackText={"Liste des rôles"}>
<div className={classes["root"]}>
<div className={classes["no-role-selected"]}>
<Typography typo={ITypo.H1Bis}>Gestion des rôles</Typography>
<div className={classes["choose-a-role"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.GREY}>
Sélectionnez un rôle
</Typography>
</div>
</div>
</div>
</DefaultRoleDashboard>
);
}
}

View File

@ -135,6 +135,22 @@
} }
} }
}, },
"Roles": {
"enabled": true,
"props": {
"path": "/roles",
"labelKey": "roles"
},
"pages": {
"RolesInformations": {
"enabled": true,
"props": {
"path": "/roles/[uid]",
"labelKey": "roles_informations"
}
}
}
},
"404": { "404": {
"enabled": true, "enabled": true,
"props": { "props": {

View File

@ -135,6 +135,22 @@
} }
} }
}, },
"Roles": {
"enabled": true,
"props": {
"path": "/roles",
"labelKey": "roles"
},
"pages": {
"RolesInformations": {
"enabled": true,
"props": {
"path": "/roles/[uid]",
"labelKey": "roles_informations"
}
}
}
},
"404": { "404": {
"enabled": true, "enabled": true,
"props": { "props": {

View File

@ -135,6 +135,22 @@
} }
} }
}, },
"Roles": {
"enabled": true,
"props": {
"path": "/roles",
"labelKey": "roles"
},
"pages": {
"RolesInformations": {
"enabled": true,
"props": {
"path": "/roles/[uid]",
"labelKey": "roles_informations"
}
}
}
},
"404": { "404": {
"enabled": true, "enabled": true,
"props": { "props": {

View File

@ -135,6 +135,22 @@
} }
} }
}, },
"Roles": {
"enabled": true,
"props": {
"path": "/roles",
"labelKey": "roles"
},
"pages": {
"RolesInformations": {
"enabled": true,
"props": {
"path": "/roles/[uid]",
"labelKey": "roles_informations"
}
}
}
},
"404": { "404": {
"enabled": true, "enabled": true,
"props": { "props": {

View File

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