Merge branch 'staging' into dev

This commit is contained in:
Maxime Lalo 2023-07-31 12:34:45 +02:00
commit af95322a94
26 changed files with 495 additions and 218 deletions

42
package-lock.json generated
View File

@ -22,7 +22,7 @@
"eslint-config-next": "13.2.4",
"form-data": "^4.0.0",
"jwt-decode": "^3.1.2",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.58",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.66",
"next": "13.2.4",
"prettier": "^2.8.7",
"react": "18.2.0",
@ -328,9 +328,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.1.tgz",
"integrity": "sha512-O7x6dMstWLn2ktjcoiNLDkAGG2EjveHL+Vvc+n0fXumkJYAcSqcVYKtwDU+hDZ0uDUsnUagSYaZrOLAYE8un1A==",
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz",
"integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==",
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
@ -441,22 +441,22 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "5.14.1",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.1.tgz",
"integrity": "sha512-mIa1WmDmNr1LoupV1Rbxt9bTFKMbIn10RHG1bnZ/FJCkAYpuU/D4n+R+ttiycgcZNngU++zyh/OQeJblzbQPzg==",
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.2.tgz",
"integrity": "sha512-x+c/MgDL1t/IIy5lDbMlrDouFG5DYZbl3DP4dbbuhlpPFBnE9glYwmJEee/orVHQpOPwLxCAIWQs+2DKSaBVWQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
}
},
"node_modules/@mui/material": {
"version": "5.14.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.1.tgz",
"integrity": "sha512-WtsgYuageTunLfxH3Ri+o1RuQTFImtRHxMcVNyD0Hhd2/znjW6KODNz0XfjvLRnNCAynBxZNiflcoIBW40h9PQ==",
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.2.tgz",
"integrity": "sha512-TgNR4/YRL11RifsnMWNhITNCkGJYVz20SCvVJBBoU5Y/KhUNSSJxjDpEB8VrnY+sUsV0NigLCkHZJglfsiS3Pw==",
"dependencies": {
"@babel/runtime": "^7.22.6",
"@mui/base": "5.0.0-beta.8",
"@mui/core-downloads-tracker": "^5.14.1",
"@mui/core-downloads-tracker": "^5.14.2",
"@mui/system": "^5.14.1",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.14.1",
@ -2386,9 +2386,9 @@
}
},
"node_modules/execa": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz",
"integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
"integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.1",
@ -3340,7 +3340,7 @@
}
},
"node_modules/le-coffre-resources": {
"resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#5c506e8b49401240b8decee77f9b7e34694f490a",
"resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#e7916d516fe434c93ef13e765d8d63a7ce3c56b2",
"license": "MIT",
"dependencies": {
"class-transformer": "^0.5.1",
@ -3361,9 +3361,9 @@
}
},
"node_modules/libphonenumber-js": {
"version": "1.10.37",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.37.tgz",
"integrity": "sha512-Z10PCaOCiAxbUxLyR31DNeeNugSVP6iv/m7UrSKS5JHziEMApJtgku4e9Q69pzzSC9LnQiM09sqsGf2ticZnMw=="
"version": "1.10.38",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.38.tgz",
"integrity": "sha512-4NjVXVUmpZ9Zsqq6FXa2+MKI+KAI3tOqA0pxXgXGluhpj4ge5didmbWJpMBqGB3AVGv1SnEtKdGTbxjSEG1kCQ=="
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
@ -4847,9 +4847,9 @@
}
},
"node_modules/tslib": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
"integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
},
"node_modules/tsutils": {
"version": "3.21.0",

View File

@ -24,7 +24,7 @@
"eslint-config-next": "13.2.4",
"form-data": "^4.0.0",
"jwt-decode": "^3.1.2",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.58",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.66",
"next": "13.2.4",
"prettier": "^2.8.7",
"react": "18.2.0",

View File

@ -0,0 +1,49 @@
import { Appointment } from "le-coffre-resources/dist/SuperAdmin";
import BaseSuperAdmin from "../BaseSuperAdmin";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetLiveVotessparams {
where?: {};
include?: {};
select?: {};
}
export type IPostLiveVotesParams = {
appointment: Appointment;
};
export type LiveVote = {
uid: string;
appointment: Appointment;
};
export default class LiveVotes extends BaseSuperAdmin {
private static instance: LiveVotes;
private readonly baseURl = this.namespaceUrl.concat("/live-votes");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
/**
* @description : Create a LiveVotes
*/
public async post(body: IPostLiveVotesParams): Promise<LiveVote> {
const url = new URL(this.baseURl);
try {
return await this.postRequest<LiveVote>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,44 @@
import { Vote } from "le-coffre-resources/dist/SuperAdmin";
import BaseSuperAdmin from "../BaseSuperAdmin";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetVotessparams {
where?: {};
include?: {};
select?: {};
}
export type IDeleteVotesParams = {
uid: Vote["uid"];
};
export default class Votes extends BaseSuperAdmin {
private static instance: Votes;
private readonly baseURl = this.namespaceUrl.concat("/votes");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
/**
* @description : Create a Votes
*/
public async delete(body: IDeleteVotesParams): Promise<Vote> {
const url = new URL(this.baseURl + "/" + body.uid);
try {
return await this.deleteRequest<Vote>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

View File

@ -37,6 +37,46 @@ export default class BurgerModal extends React.Component<IProps, IState> {
text="Collaborateurs"
routesActive={[Module.getInstance().get().modules.pages.Collaborators.props.path]}
/>
<NavigationLink
path={Module.getInstance().get().modules.pages.DeedTypes.props.path}
text="Paramétrage des listes de pièces"
routesActive={[
Module.getInstance().get().modules.pages.DeedTypes.props.path,
Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path,
Module.getInstance().get().modules.pages.DeedTypes.pages.Edit.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.Edit.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.Create.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.props.path,
]}
/>
<NavigationLink
path={Module.getInstance().get().modules.pages.Roles.props.path}
text="Gestion des rôles"
routesActive={[
Module.getInstance().get().modules.pages.Roles.props.path,
Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path,
]}
/>
<NavigationLink
path={Module.getInstance().get().modules.pages.Users.props.path}
text="Gestion des utilisateurs"
routesActive={[
Module.getInstance().get().modules.pages.Users.props.path,
Module.getInstance().get().modules.pages.Users.pages.UsersInformations.props.path,
]}
/>
<NavigationLink
path={Module.getInstance().get().modules.pages.Offices.props.path}
text="Gestion des offices"
routesActive={[
Module.getInstance().get().modules.pages.Offices.props.path,
Module.getInstance().get().modules.pages.Offices.pages.OfficesInformations.props.path,
]}
/>
<NavigationLink path={Module.getInstance().get().modules.pages.MyAccount.props.path} text="Mon compte" />
<NavigationLink text="CGU" />
<div className={classes["separator"]} />
<LogOutButton />
</div>

View File

@ -58,7 +58,6 @@ export default class ProfileModal extends React.Component<IProps, IState> {
Module.getInstance().get().modules.pages.Offices.pages.OfficesInformations.props.path,
]}
/>
<NavigationLink text="Gestion des noms de domaine" />
<NavigationLink text="CGU" />
<div className={classes["separator"]} />
<LogOutButton />

View File

@ -3,6 +3,7 @@
.root {
color: $black;
vertical-align: center;
font-family: "Inter", sans-serif;
&.H1-60 {
font-style: normal;

View File

@ -34,7 +34,7 @@ export default function DocumentTypeListContainer(props: IProps) {
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
console.log("Block selected :", block);
const redirectPath = Module.getInstance().get().modules.pages.DocumentTypes.pages.Edit.props.path;
const redirectPath = Module.getInstance().get().modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],

View File

@ -38,6 +38,7 @@
}
.second-line {
margin-top: 32px;
max-width: 426px;
}
.third-line {
margin-top: 32px;

View File

@ -4,7 +4,7 @@
.header {
display: flex;
justify-content: space-between;
align-items: flex-end;
align-items: flex-start;
@media (max-width: $screen-l) {
flex-direction: column;

View File

@ -15,7 +15,7 @@ export default class DeedTypes extends BasePage<IProps, IState> {
<Typography typo={ITypo.H1Bis}>Paramétrage des listes de pièces</Typography>
<div className={classes["choose-a-role"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.GREY}>
Sélectionnez une liste de pièces
Sélectionnez un type d'acte
</Typography>
</div>
</div>

View File

@ -5,6 +5,7 @@ import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard";
import Module from "@Front/Config/Module";
import { validateOrReject } from "class-validator";
import { DocumentType } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router";
@ -39,12 +40,19 @@ export default function DocumentTypesEdit() {
});
await validateOrReject(documentToUpdate, { groups: ["updateDocumentType"] });
const documentTypeUpdated = await DocumentTypes.getInstance().put(documentTypeUid as string, documentToUpdate);
console.log(documentTypeUpdated);
router.push(
Module.getInstance()
.get()
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace(
"[uid]",
documentTypeUpdated.uid ?? "",
),
);
} catch (e) {
console.log(e);
}
},
[documentTypeUid],
[documentTypeUid, router],
);
return (

View File

@ -1,91 +1,39 @@
@import "@Themes/constants.scss";
.root {
.header {
.document-infos {
display: flex;
justify-content: space-between;
align-items: flex-end;
@media (max-width: $screen-l) {
flex-direction: column;
align-items: flex-start;
gap: 24px;
}
}
.subtitle {
margin-top: 32px;
}
.deed-type-container {
margin-top: 32px;
display: flex;
gap: 100px;
justify-content: space-between;
gap: 24px;
padding: 24px;
margin-top: 32px;
background-color: var(--grey-soft);
@media (max-width: $screen-l) {
gap: 80px;
}
@media (max-width: $screen-m) {
.left {
display: flex;
flex-direction: column;
gap: 32px;
justify-content: space-between;
@media (max-width: $screen-l) {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32px;
}
.infos {
display: flex;
gap: 100px;
flex: 1;
@media (max-width: $screen-s) {
grid-template-columns: repeat(1, 1fr);
}
@media (max-width: $screen-l) {
.document-infos-row {
display: flex;
flex-direction: column;
gap: 32px;
}
.box {
.box-title {
margin-bottom: 8px;
opacity: 0.4;
gap: 12px;
}
}
.middle-box {
flex: 1;
}
}
.pencil {
align-self: center;
@media (max-width: $screen-m) {
align-self: flex-start;
.right {
}
}
}
.documents-container {
margin-top: 32px;
padding: 32px 16px;
border: 1px solid var(--grey);
.container-title {
}
.documents {
margin-top: 32px;
}
.button-container {
margin-top: 32px;
}
}
.delete-container {
display: flex;
justify-content: center;
margin-top: 32px;
}
}

View File

@ -1,89 +1,72 @@
import ChevronIcon from "@Assets/Icons/chevron.svg";
import PenICon from "@Assets/Icons/pen.svg";
import DocumentTypes from "@Front/Api/LeCoffreApi/Admin/DocumentTypes/DocumentTypes";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard";
import Module from "@Front/Config/Module";
import classNames from "classnames";
import { DocumentType } from "le-coffre-resources/dist/Admin";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import classes from "./classes.module.scss";
type IProps = {};
export default function DocumentTypesInformations(props: IProps) {
export default function DocumentTypesInformations() {
const router = useRouter();
let { documentTypeUid } = router.query;
const [documentTypeSelected, setDocumentTypeSelected] = useState<DocumentType | null>(null);
const [documentSelected, setDocumentSelected] = useState<DocumentType | null>(null);
useEffect(() => {
async function getDocumentType() {
async function getDocument() {
if (!documentTypeUid) return;
const documentType = await DocumentTypes.getInstance().getByUid(documentTypeUid as string);
setDocumentTypeSelected(documentType);
const document = await DocumentTypes.getInstance().getByUid(documentTypeUid as string, {
_count: true,
});
if (!document) return;
setDocumentSelected(document);
}
getDocumentType();
getDocument();
}, [documentTypeUid]);
const onSubmitHandler = useCallback(async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {}, []);
return (
<DefaultDocumentTypesDashboard mobileBackText={"Liste des types d'actes"}>
<DefaultDocumentTypesDashboard mobileBackText={"Liste des collaborateurs"}>
<div className={classes["root"]}>
<div className={classes["header"]}>
<Typography typo={ITypo.H1Bis}>Paramétrage des listes de pièces</Typography>
<Button variant={EButtonVariant.LINE}>
Modifier la liste des documents
<Image src={ChevronIcon} alt="Chevron" />
</Button>
<div className={classes["folder-header"]}>
<Typography typo={ITypo.H1Bis}>Paramétrage des documents</Typography>
</div>
<div className={classes["subtitle"]}>
<Typography typo={ITypo.H3}>{documentTypeSelected?.name}</Typography>
</div>
<div className={classes["deed-type-container"]}>
<div className={classes["infos"]}>
<div className={classes["box"]}>
<Typography typo={ITypo.NAV_INPUT_16} className={classes["box-title"]} color={ITypoColor.BLACK}>
Nom du type d'acte
<div className={classes["document-infos"]}>
<div className={classes["left"]}>
<div className={classes["document-infos-row"]}>
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
Nom du document
</Typography>
<Typography typo={ITypo.P_18}>{documentTypeSelected?.name}</Typography>
<Typography typo={ITypo.P_18}>{documentSelected?.name}</Typography>
</div>
<div className={classNames(classes["middle-box"], classes["box"])}>
<Typography typo={ITypo.NAV_INPUT_16} className={classes["box-title"]} color={ITypoColor.BLACK}>
Description
<div className={classes["document-infos-row"]}>
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
Description visible par les collaborateurs de l'office
</Typography>
<Typography typo={ITypo.P_18}>{documentSelected?.private_description}</Typography>
</div>
<div className={classes["document-infos-row"]}>
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
Description visible par les clients de l'office
</Typography>
<Typography typo={ITypo.P_18}>{documentSelected?.public_description}</Typography>
</div>
</div>
<div className={classes["pencil"]}>
<div className={classes["right"]}>
<Link
href={Module.getInstance()
.get()
.modules.pages.DocumentTypes.pages.Edit.props.path.replace("[uid]", documentTypeUid as string)}
.modules.pages.DocumentTypes.pages.Edit.props.path.replace("[uid]", documentSelected?.uid ?? "")}
className={classes["edit-icon-container"]}>
<Image src={PenICon} alt="éditer le type d'acte" />
<Image src={PenICon} alt="edit informations" />
</Link>
</div>
</div>
<div className={classes["documents-container"]}>
<Form onSubmit={onSubmitHandler}>
<div className={classes["container-title"]}>
<Typography typo={ITypo.P_SB_18}>Documents paramétrés</Typography>
</div>
<div className={classes["button-container"]}>
<Button type="submit">Enregistrer</Button>
</div>
</Form>
</div>
<div className={classes["delete-container"]}>
<Button variant={EButtonVariant.GHOST}>Supprimer</Button>
</div>
</div>
</DefaultDocumentTypesDashboard>
);

View File

@ -18,7 +18,7 @@ export default function Login() {
const redirectUserOnConnection = useCallback(() => {
async function getUser() {
try {
await UserStore.instance.connect(process.env["NEXT_PUBLIC_ADMIN_ID"] as string);
await UserStore.instance.connect("jelkvelknvlkn");
await JwtService.getInstance().checkJwt();
router.push(Module.getInstance().get().modules.pages.Folder.props.path);
} catch (e) {

View File

@ -47,31 +47,31 @@ export default function OfficeInformations(props: IProps) {
</div>
<div key={user.uid} className={classes["user-line-mobile"]}>
<div className={classes["line"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_16} color={ITypoColor.BLACK}>
Nom
</Typography>
<Typography typo={ITypo.P_16}>{user.contact?.last_name}</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_16} color={ITypoColor.BLACK}>
Prénom
</Typography>
<Typography typo={ITypo.P_16}>{user.contact?.first_name}</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
E-mail pro
<Typography typo={ITypo.P_SB_16} color={ITypoColor.BLACK}>
E-mail
</Typography>
<Typography typo={ITypo.P_16}>{user.contact?.email}</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
Numéro de téléphone
<Typography typo={ITypo.P_SB_16} color={ITypoColor.BLACK}>
Téléphone
</Typography>
<Typography typo={ITypo.P_16}>{user.contact?.phone_number}</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_16} color={ITypoColor.BLACK}>
Rôle
</Typography>
<Typography typo={ITypo.P_16}>{user.office_role ? user.office_role?.name : user.role?.name}</Typography>
@ -118,19 +118,19 @@ export default function OfficeInformations(props: IProps) {
</div>
<div className={classes["users-container"]}>
<div className={classes["first-line"]}>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Nom
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Prénom
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
E-mail pro
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
E-mail
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
Numéro de téléphone
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Téléphone
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Rôle
</Typography>
</div>
@ -155,19 +155,19 @@ export default function OfficeInformations(props: IProps) {
</div>
<div className={classes["users-container"]}>
<div className={classes["first-line"]}>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Nom
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Prénom
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
E-mail pro
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
E-mail
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
Numéro de téléphone
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Téléphone
</Typography>
<Typography typo={ITypo.NAV_HEADER_18} color={ITypoColor.BLACK}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Rôle
</Typography>
</div>

View File

@ -0,0 +1,28 @@
.root {
position: relative;
.background-container {
width: 100%;
height: 100%;
position: absolute;
z-index: -1;
> img {
width: 100%;
object-fit: cover;
}
}
.select-folder-container {
max-width: 530px;
padding: 80px 72px;
display: flex;
flex-direction: column;
justify-content: center;
gap: 48px;
margin: auto;
background-color: white;
.title {
text-align: center;
}
}
}

View File

@ -0,0 +1,61 @@
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import classes from "./classes.module.scss";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import { OfficeFolder } from "le-coffre-resources/dist/Customer";
import { useCallback, useEffect, useState } from "react";
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import BackgroundImage from "@Assets/images/background.png";
import Image from "next/image";
export default function SelectFolder() {
const [folders, setFolders] = useState<OfficeFolder[]>([]);
const router = useRouter();
useEffect(() => {
async function getFolders() {
const folders = await Folders.getInstance().get({});
setFolders(folders.slice(0, 3));
}
getFolders();
}, []);
const handleSelectBlock = useCallback(
(block: IBlock) => {
router.push(
Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", block.id),
);
},
[router],
);
return (
<DefaultTemplate title="Sélectionner un dossier" hasHeaderLinks={false}>
<div className={classes["root"]}>
<div className={classes["background-container"]}>
<Image src={BackgroundImage} alt="background" />
</div>
<div className={classes["select-folder-container"]}>
<div className={classes["title"]}>
<Typography typo={ITypo.H1}>Vos dossiers</Typography>
</div>
<div className={classes["folders-container"]}>
<BlockList
onSelectedBlock={handleSelectBlock}
blocks={folders.map((folder) => {
return {
id: folder.uid!,
name: folder.name!,
selected: false,
};
})}
/>
</div>
</div>
</div>
</DefaultTemplate>
);
}

View File

@ -79,3 +79,7 @@
}
}
}
.remove-my-vote {
margin-top: 16px;
}

View File

@ -1,13 +1,19 @@
import WarningIcon from "@Assets/images/warning.png";
import OfficeRoles from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
import Roles from "@Front/Api/LeCoffreApi/Admin/Roles/Roles";
import LiveVotes from "@Front/Api/LeCoffreApi/SuperAdmin/LiveVotes/LiveVotes";
import Users from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users";
import Votes from "@Front/Api/LeCoffreApi/SuperAdmin/Votes/Votes";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import SelectField, { IOption } from "@Front/Components/DesignSystem/Form/SelectField";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import Switch from "@Front/Components/DesignSystem/Switch";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultUserDashboard from "@Front/Components/LayoutTemplates/DefaultUserDashboard";
import JwtService from "@Front/Services/JwtService/JwtService";
import Toasts from "@Front/Stores/Toasts";
import User from "le-coffre-resources/dist/Notary";
import User, { Appointment, Vote } from "le-coffre-resources/dist/SuperAdmin";
import { EAppointmentStatus, EVote } from "le-coffre-resources/dist/SuperAdmin/Appointment";
import Image from "next/image";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
@ -30,6 +36,46 @@ export default function UserInformations(props: IProps) {
const [isAdminChecked, setIsAdminChecked] = useState<boolean>(false);
const [isAdminModalOpened, setIsAdminModalOpened] = useState<boolean>(false);
const [currentAppointment, setCurrentAppointment] = useState<Appointment | null>(null);
/** When page change, get the user of the page */
const getUser = useCallback(async () => {
if (!userUid) return;
const user = await Users.getInstance().getByUid(userUid as string, {
q: {
contact: true,
office_role: true,
office_membership: true,
role: true,
appointment: {
include: {
votes: {
include: {
voter: true,
},
},
},
},
votes: 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);
}, [userUid]);
useEffect(() => {
getUser();
}, [getUser, userUid]);
useEffect(() => {
if (!userSelected) return;
setCurrentAppointment(userSelected?.appointment?.find((appointment) => appointment.status === EAppointmentStatus.OPEN) ?? null);
}, [userSelected]);
/** Functions for the admin modal */
const openAdminModal = () => {
setIsAdminModalOpened(true);
@ -49,9 +95,23 @@ export default function UserInformations(props: IProps) {
const handleAdminModalAccepted = useCallback(async () => {
if (!userSelected) return;
if (adminModalType === "add") {
// add super admin
const adminRole = await Roles.getInstance().getOne({
where: {
name: "admin",
},
});
if (!adminRole) return;
await Users.getInstance().put(
userSelected?.uid as string,
User.hydrate<User>({
uid: userSelected?.uid as string,
office_role: undefined,
role: adminRole,
}),
);
} else {
// remove super admin
// retirer rôle admin
}
setIsAdminModalOpened(false);
}, [userSelected, adminModalType]);
@ -74,21 +134,27 @@ export default function UserInformations(props: IProps) {
const handleSuperAdminModalAccepted = useCallback(async () => {
if (!userSelected) return;
if (superAdminModalType === "add") {
Toasts.getInstance().open({
title: "Vote attribué",
text: "Vous avez voté pour attribuer le titre de Super Admin à " + userSelected.contact?.first_name,
let vote = Vote.hydrate<Vote>({
appointment: Appointment.hydrate<Appointment>({
uid: currentAppointment?.uid ?? undefined,
targeted_user: User.hydrate<User>({
uid: userSelected.uid,
}),
choice: superAdminModalType === "add" ? EVote.NOMINATE : EVote.DISMISS,
}),
});
// add super admin
} else {
const liveVote = await LiveVotes.getInstance().post(vote);
if (liveVote.appointment.votes?.length === 3) {
Toasts.getInstance().open({
title: "Vote attribué",
text: "Vous avez voté pour supprimer le titre de Super Admin à " + userSelected.contact?.first_name,
title: `Le titre de super-administrateur a été attribué à ${userSelected.contact?.first_name} ${userSelected.contact?.last_name} `,
});
// remove super admin
}
await getUser();
setIsSuperAdminModalOpened(false);
}, [userSelected, superAdminModalType]);
}, [userSelected, currentAppointment, superAdminModalType, getUser]);
/** Reset switch state when userSelect change */
useEffect(() => {
@ -97,28 +163,20 @@ export default function UserInformations(props: IProps) {
setIsAdminChecked(userSelected.role?.name === "admin" && !userSelected.office_role);
}, [userSelected]);
/** When page change, get the user of the page */
useEffect(() => {
async function getUser() {
if (!userUid) return;
const user = await Users.getInstance().getByUid(userUid as string, {
q: {
contact: true,
office_role: true,
office_membership: true,
role: true,
},
});
if (!user) return;
const userHasVoted = useCallback(() => {
if (!currentAppointment) return false;
const user = JwtService.getInstance().decodeJwt();
return currentAppointment.votes?.find((vote) => vote.voter?.uid === user?.userId) !== undefined;
}, [currentAppointment]);
const roles = await OfficeRoles.getInstance().get();
if (!roles) return;
setAvailableRoles(roles.map((role) => ({ value: role.uid, label: role.name })));
setUserSelected(user);
}
getUser();
}, [userUid]);
const deleteMyVote = useCallback(async () => {
if (!currentAppointment) return;
const user = JwtService.getInstance().decodeJwt();
const vote = currentAppointment.votes?.find((vote) => vote.voter?.uid === user?.userId);
if (!vote) return;
await Votes.getInstance().delete({ uid: vote.uid });
await getUser();
}, [currentAppointment, getUser]);
return (
<DefaultUserDashboard mobileBackText={"Liste des utilisateurs"}>
@ -180,22 +238,36 @@ export default function UserInformations(props: IProps) {
<div className={classes["second-line"]}>
<Switch label="Admin de son office" checked={isAdminChecked} onChange={handleAdminChanged} />
<Switch label="Super-admin LeCoffre.io" checked={isSuperAdminChecked} onChange={handleSuperAdminChanged} />
{currentAppointment && (
<div className={classes["votes-block"]}>
<div className={classes["left"]}>
<Image src={WarningIcon} alt="warning" width={28} height={28} />
</div>
<div className={classes["right"]}>
<div>
<Typography typo={ITypo.P_SB_18}>1/3</Typography>
<Typography typo={ITypo.P_SB_18}>{currentAppointment.votes?.length}/3</Typography>
</div>
<div>
<Typography typo={ITypo.CAPTION_14}>
Vous avez voté pour attribuer le titre de Super Admin. Il manque 2 votes pour que le
collaborateur se voit attribuer le titre.
{currentAppointment.choice === EVote.NOMINATE
? `Un ou des collaborateurs souhaitent attribuer le titre de Super Admin à ce collaborateur. Il manque ${
3 - currentAppointment.votes?.length!
} vote(s) pour que le collaborateur se voit attribuer le titre.`
: `Un ou des collaborateurs souhaitent retirer le titre de Super Admin à ce collaborateur. Il manque ${
3 - currentAppointment.votes?.length!
} vote(s) pour que le collaborateur se voit retirer le titre.`}
</Typography>
</div>
{userHasVoted() && (
<div className={classes["remove-my-vote"]}>
<Button variant={EButtonVariant.SECONDARY} onClick={deleteMyVote}>
Retirer mon vote
</Button>
</div>
)}
</div>
</div>
)}
</div>
</div>
</div>
@ -221,9 +293,15 @@ export default function UserInformations(props: IProps) {
onClose={closeAdminModal}
onAccept={handleAdminModalAccepted}
closeBtn
header={`Souhaitez-vous ${adminModalType === "add" ? "ajouter" : "retirer"} ${
header={
adminModalType === "add"
? `Souhaitez-vous nommer ${
userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name
} aux administrateurs de son office ?`}
} administrateur de son office ?`
: `Souhaitez-vous retirer le rôle administrateur de son office à ${
userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name
} ?`
}
confirmText={adminModalType === "add" ? "Ajouter" : "Retirer"}
cancelText={"Annuler"}>
<div className={classes["modal-content"]}></div>

View File

@ -109,6 +109,13 @@
}
}
}
},
"Select": {
"enabled": true,
"props": {
"path": "/folders/select",
"labelKey": "select_folder"
}
}
}
},

View File

@ -109,6 +109,13 @@
}
}
}
},
"Select": {
"enabled": true,
"props": {
"path": "/folders/select",
"labelKey": "select_folder"
}
}
}
},

View File

@ -109,6 +109,13 @@
}
}
}
},
"Select": {
"enabled": true,
"props": {
"path": "/folders/select",
"labelKey": "select_folder"
}
}
}
},

View File

@ -109,6 +109,13 @@
}
}
}
},
"Select": {
"enabled": true,
"props": {
"path": "/folders/select",
"labelKey": "select_folder"
}
}
}
},

View File

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