From 9332016dc1084a47fc3d929fd318037ade93dbc5 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Tue, 18 Jul 2023 13:36:25 +0200 Subject: [PATCH] :sparkles: Super admin page users --- .../Api/LeCoffreApi/Admin/Users/Users.ts | 91 ++++++++++++++ .../DefaultCollaboratorDashboard/index.tsx | 4 +- .../UserListContainer/classes.module.scss | 21 ++++ .../UserListContainer/index.tsx | 64 ++++++++++ .../DefaultUserDashboard/classes.module.scss | 117 ++++++++++++++++++ .../DefaultUserDashboard/index.tsx | 115 +++++++++++++++++ .../UserInformations/classes.module.scss | 54 ++++++++ .../Layouts/Users/UserInformations/index.tsx | 117 ++++++++++++++++++ .../Layouts/Users/classes.module.scss | 72 +++++++++++ src/front/Components/Layouts/Users/index.tsx | 26 ++++ src/front/Config/Module/development.json | 16 +++ src/front/Config/Module/preprod.json | 16 +++ src/front/Config/Module/production.json | 16 +++ src/front/Config/Module/staging.json | 16 +++ src/pages/users/[userUid]/index.tsx | 5 + src/pages/users/index.tsx | 5 + 16 files changed, 753 insertions(+), 2 deletions(-) create mode 100644 src/front/Api/LeCoffreApi/Admin/Users/Users.ts create mode 100644 src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/classes.module.scss create mode 100644 src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/index.tsx create mode 100644 src/front/Components/LayoutTemplates/DefaultUserDashboard/classes.module.scss create mode 100644 src/front/Components/LayoutTemplates/DefaultUserDashboard/index.tsx create mode 100644 src/front/Components/Layouts/Users/UserInformations/classes.module.scss create mode 100644 src/front/Components/Layouts/Users/UserInformations/index.tsx create mode 100644 src/front/Components/Layouts/Users/classes.module.scss create mode 100644 src/front/Components/Layouts/Users/index.tsx create mode 100644 src/pages/users/[userUid]/index.tsx create mode 100644 src/pages/users/index.tsx diff --git a/src/front/Api/LeCoffreApi/Admin/Users/Users.ts b/src/front/Api/LeCoffreApi/Admin/Users/Users.ts new file mode 100644 index 00000000..6e50f59a --- /dev/null +++ b/src/front/Api/LeCoffreApi/Admin/Users/Users.ts @@ -0,0 +1,91 @@ +import User from "le-coffre-resources/dist/SuperAdmin"; +import BaseAdmin from "../BaseAdmin"; + +// TODO Type get query params -> Where + inclue + orderby +export interface IGetUsersparams { + where?: {}; + include?: {}; + select?: {}; +} + +// TODO Type getbyuid query params + +export type IPutUsersParams = { + uid?: User["uid"]; + idNot?: User["idNot"]; + contact?: User["contact"]; + office_membership?: User["office_membership"]; + documents?: User["documents"]; +}; + +export default class Users extends BaseAdmin { + private static instance: Users; + private readonly baseURl = this.namespaceUrl.concat("/users"); + + private constructor() { + super(); + } + + public static getInstance() { + if (!this.instance) { + return new this(); + } else { + return this.instance; + } + } + + /** + * @description : Get all Users + */ + public async get(q: IGetUsersparams): Promise { + 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(url); + } catch (err) { + this.onError(err); + return Promise.reject(err); + } + } + + /** + * @description : Get a folder by uid + */ + public async getByUid(uid: string, q?: any): Promise { + 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(url); + } catch (err) { + this.onError(err); + return Promise.reject(err); + } + } + + /** + * @description : Create a User + */ + // public async post(body: IPostDeedsParams): Promise { + // const url = new URL(this.baseURl); + // try { + // return await this.postRequest(url, body); + // } catch (err) { + // this.onError(err); + // return Promise.reject(err); + // } + // } + + /** + * @description : Update the folder description + */ + public async put(uid: string, body: IPutUsersParams): Promise { + const url = new URL(this.baseURl.concat(`/${uid}`)); + try { + return await this.putRequest(url, body); + } catch (err) { + this.onError(err); + return Promise.reject(err); + } + } +} diff --git a/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx b/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx index 1c2067f5..bc5195ff 100644 --- a/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx +++ b/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx @@ -9,7 +9,7 @@ import Image from "next/image"; import React, { ReactNode } from "react"; import classes from "./classes.module.scss"; -import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; +import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/Admin/Users/Users"; import User from "le-coffre-resources/dist/Notary"; import CollaboratorListContainer from "./CollaboratorListContainer"; @@ -88,7 +88,7 @@ export default class DefaultCollaboratorDashboard extends React.Component this.onResize(window)); const query: IGetUsersparams = { - where: { office_uid: "6981326f-8a0a-4437-b15c-4cd5c4d80f6e" }, + where: { office_uid: "2af8694e-4dac-4e0d-854a-acb7fed8aa7d" }, include: { contact: true }, }; diff --git a/src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/classes.module.scss b/src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/classes.module.scss new file mode 100644 index 00000000..300d46f2 --- /dev/null +++ b/src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/classes.module.scss @@ -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); + } +} diff --git a/src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/index.tsx b/src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/index.tsx new file mode 100644 index 00000000..8128b38d --- /dev/null +++ b/src/front/Components/LayoutTemplates/DefaultUserDashboard/UserListContainer/index.tsx @@ -0,0 +1,64 @@ +import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList"; +import SearchBar from "@Front/Components/DesignSystem/SearchBar"; +import Module from "@Front/Config/Module"; +import User from "le-coffre-resources/dist/Notary"; +import { useRouter } from "next/router"; +import React, { useCallback, useState } from "react"; + +import classes from "./classes.module.scss"; + +type IProps = { + users: User[]; + onSelectedUser?: (user: User) => void; + onCloseLeftSide?: () => void; +}; + +export default function UserListContainer(props: IProps) { + const [filteredUsers, setFilteredUsers] = useState(props.users); + const router = useRouter(); + + const { userUid } = router.query; + const filterUsers = useCallback( + (input: string) => { + const filteredUsers = props.users.filter((user) => { + return ( + user.contact?.first_name?.toLowerCase().includes(input.toLowerCase()) || + user.contact?.last_name?.toLowerCase().includes(input.toLowerCase()) + ); + }); + setFilteredUsers(filteredUsers); + }, + [props.users], + ); + + const onSelectedBlock = useCallback( + (block: IBlock) => { + props.onCloseLeftSide && props.onCloseLeftSide(); + const redirectPath = Module.getInstance().get().modules.pages.Users.pages.UsersInformations.props.path; + router.push(redirectPath.replace("[uid]", block.id)); + }, + [props, router], + ); + + return ( +
+
+
+ +
+
+ { + return { + name: user.contact?.first_name + " " + user.contact?.last_name, + id: user.uid!, + selected: user.uid === userUid, + }; + })} + onSelectedBlock={onSelectedBlock} + /> +
+
+
+ ); +} diff --git a/src/front/Components/LayoutTemplates/DefaultUserDashboard/classes.module.scss b/src/front/Components/LayoutTemplates/DefaultUserDashboard/classes.module.scss new file mode 100644 index 00000000..a5ba79a3 --- /dev/null +++ b/src/front/Components/LayoutTemplates/DefaultUserDashboard/classes.module.scss @@ -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; + } + } + } + } +} diff --git a/src/front/Components/LayoutTemplates/DefaultUserDashboard/index.tsx b/src/front/Components/LayoutTemplates/DefaultUserDashboard/index.tsx new file mode 100644 index 00000000..c65a6560 --- /dev/null +++ b/src/front/Components/LayoutTemplates/DefaultUserDashboard/index.tsx @@ -0,0 +1,115 @@ +import ChevronIcon from "@Assets/Icons/chevron.svg"; +import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; +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 User from "le-coffre-resources/dist/Notary"; +import Image from "next/image"; +import React, { ReactNode } from "react"; + +import classes from "./classes.module.scss"; +import UserListContainer from "./UserListContainer"; + +type IProps = { + title: string; + children?: ReactNode; + onSelectedUser: (user: User) => void; + hasBackArrow: boolean; + backArrowUrl?: string; + mobileBackText?: string; +}; +type IState = { + users: User[] | null; + isLeftSideOpen: boolean; + leftSideCanBeClosed: boolean; +}; + +export default class DefaultUserDashboard extends React.Component { + private onWindowResize = () => {}; + public static defaultProps: Partial = { + hasBackArrow: false, + }; + + public constructor(props: IProps) { + super(props); + this.state = { + users: 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 ( +
+
+
+ {this.state.isLeftSideOpen &&
} +
+ {this.state.users && } +
+
+ open side menu +
+ +
+ {this.props.hasBackArrow && ( +
+ +
+ )} + {this.props.mobileBackText && ( +
+ +
+ )} + {this.props.children} +
+
+ +
+ ); + } + + public override async componentDidMount() { + this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window)); + const query: IGetUsersparams = { + include: { contact: true }, + }; + + const users = await Users.getInstance().get(query); + this.setState({ users }); + } + 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 }); + } +} diff --git a/src/front/Components/Layouts/Users/UserInformations/classes.module.scss b/src/front/Components/Layouts/Users/UserInformations/classes.module.scss new file mode 100644 index 00000000..3b68e9c8 --- /dev/null +++ b/src/front/Components/Layouts/Users/UserInformations/classes.module.scss @@ -0,0 +1,54 @@ +@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; + + display: flex; + gap: 32px; + .part { + flex: 1; + .first-line { + display: flex; + justify-content: space-between; + } + .second-line { + margin-top: 32px; + display: flex; + gap: 16px; + flex-direction: column; + } + } + .third-line { + margin-top: 32px; + } + } +} diff --git a/src/front/Components/Layouts/Users/UserInformations/index.tsx b/src/front/Components/Layouts/Users/UserInformations/index.tsx new file mode 100644 index 00000000..e029c46c --- /dev/null +++ b/src/front/Components/Layouts/Users/UserInformations/index.tsx @@ -0,0 +1,117 @@ +import Users from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; +import CheckBox from "@Front/Components/DesignSystem/CheckBox"; +import SelectField, { IOption } from "@Front/Components/DesignSystem/Form/SelectField"; +import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; +import DefaultUserDashboard from "@Front/Components/LayoutTemplates/DefaultUserDashboard"; +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 OfficeRoles from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles"; + +type IProps = {}; +export default function UserInformations(props: IProps) { + const router = useRouter(); + let { userUid } = router.query; + + const [userSelected, setUserSelected] = useState(null); + const [availableRoles, setAvailableRoles] = useState([]); + + useEffect(() => { + async function getUser() { + if (!userUid) return; + const user = await Users.getInstance().getByUid(userUid as string, { + q: { + contact: true, + office_role: true, + }, + }); + if (!user) return; + + const roles = await OfficeRoles.getInstance().get(); + if (!roles) return; + setAvailableRoles(roles.map((role) => ({ value: role.uid, label: role.name }))); + setUserSelected(user); + } + + getUser(); + }, [userUid]); + + return ( + +
+
+ {userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name} +
+
+
+ + Nom + + {userSelected?.contact?.first_name} +
+
+ + Prénom + + {userSelected?.contact?.last_name} +
+
+ + Numéro de téléphone + + {userSelected?.contact?.phone_number} +
+
+ + Email + + {userSelected?.contact?.email} +
+
+ +
+
+
+ Rôle au sein de son office +
+
+ +
+
+
+
+ Attribuer un titre +
+
+ + +
+
+
+
+
+ ); +} diff --git a/src/front/Components/Layouts/Users/classes.module.scss b/src/front/Components/Layouts/Users/classes.module.scss new file mode 100644 index 00000000..a425a01a --- /dev/null +++ b/src/front/Components/Layouts/Users/classes.module.scss @@ -0,0 +1,72 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + align-items: center; + flex-direction: column; + min-height: 100%; + + .no-folder-selected { + width: 100%; + + .choose-a-folder { + margin-top: 96px; + text-align: center; + } + } + + .folder-informations { + width: 100%; + min-height: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + flex-grow: 1; + + .folder-header { + width: 100%; + + .header { + margin-bottom: 32px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + } + } + + .second-box { + margin-top: 24px; + margin-bottom: 32px; + } + + .progress-bar { + margin-bottom: 32px; + } + + .button-container { + width: 100%; + text-align: center; + :first-child { + margin-right: 12px; + } + > * { + margin: auto; + } + @media (max-width: $screen-m) { + :first-child { + margin-right: 0; + margin-bottom: 12px; + } + > * { + width: 100%; + } + } + } + .modal-title { + margin-bottom: 24px; + } + } +} diff --git a/src/front/Components/Layouts/Users/index.tsx b/src/front/Components/Layouts/Users/index.tsx new file mode 100644 index 00000000..c686a669 --- /dev/null +++ b/src/front/Components/Layouts/Users/index.tsx @@ -0,0 +1,26 @@ +import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; + +import BasePage from "../Base"; +import classes from "./classes.module.scss"; +import DefaultUserDashboard from "@Front/Components/LayoutTemplates/DefaultUserDashboard"; + +type IProps = {}; +type IState = {}; +export default class Users extends BasePage { + public override render(): JSX.Element { + return ( + +
+
+ Informations des utilisateurs +
+ + Sélectionnez un utilisateur + +
+
+
+
+ ); + } +} diff --git a/src/front/Config/Module/development.json b/src/front/Config/Module/development.json index e2a56d46..bf5808bd 100644 --- a/src/front/Config/Module/development.json +++ b/src/front/Config/Module/development.json @@ -181,6 +181,22 @@ } } }, + "Users": { + "enabled": true, + "props": { + "path": "/users", + "labelKey": "users" + }, + "pages": { + "UsersInformations": { + "enabled": true, + "props": { + "path": "/users/[uid]", + "labelKey": "users_informations" + } + } + } + }, "404": { "enabled": true, "props": { diff --git a/src/front/Config/Module/preprod.json b/src/front/Config/Module/preprod.json index e2a56d46..bf5808bd 100644 --- a/src/front/Config/Module/preprod.json +++ b/src/front/Config/Module/preprod.json @@ -181,6 +181,22 @@ } } }, + "Users": { + "enabled": true, + "props": { + "path": "/users", + "labelKey": "users" + }, + "pages": { + "UsersInformations": { + "enabled": true, + "props": { + "path": "/users/[uid]", + "labelKey": "users_informations" + } + } + } + }, "404": { "enabled": true, "props": { diff --git a/src/front/Config/Module/production.json b/src/front/Config/Module/production.json index e2a56d46..bf5808bd 100644 --- a/src/front/Config/Module/production.json +++ b/src/front/Config/Module/production.json @@ -181,6 +181,22 @@ } } }, + "Users": { + "enabled": true, + "props": { + "path": "/users", + "labelKey": "users" + }, + "pages": { + "UsersInformations": { + "enabled": true, + "props": { + "path": "/users/[uid]", + "labelKey": "users_informations" + } + } + } + }, "404": { "enabled": true, "props": { diff --git a/src/front/Config/Module/staging.json b/src/front/Config/Module/staging.json index e2a56d46..bf5808bd 100644 --- a/src/front/Config/Module/staging.json +++ b/src/front/Config/Module/staging.json @@ -181,6 +181,22 @@ } } }, + "Users": { + "enabled": true, + "props": { + "path": "/users", + "labelKey": "users" + }, + "pages": { + "UsersInformations": { + "enabled": true, + "props": { + "path": "/users/[uid]", + "labelKey": "users_informations" + } + } + } + }, "404": { "enabled": true, "props": { diff --git a/src/pages/users/[userUid]/index.tsx b/src/pages/users/[userUid]/index.tsx new file mode 100644 index 00000000..6886fefb --- /dev/null +++ b/src/pages/users/[userUid]/index.tsx @@ -0,0 +1,5 @@ +import UserInformations from "@Front/Components/Layouts/Users/UserInformations"; + +export default function Route() { + return ; +} diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx new file mode 100644 index 00000000..579670dd --- /dev/null +++ b/src/pages/users/index.tsx @@ -0,0 +1,5 @@ +import Users from "@Front/Components/Layouts/Users"; + +export default function Route() { + return ; +}