Merge branch 'dev-without-id-not' into dev

This commit is contained in:
Maxime Lalo 2023-09-21 17:30:03 +02:00
commit 26d597411f
18 changed files with 363 additions and 95 deletions

View File

@ -75,6 +75,11 @@ class FolderListContainerClass extends React.Component<IPropsClass, IState> {
); );
} }
public override componentDidUpdate(prevProps: Readonly<IPropsClass>, prevState: Readonly<IState>, snapshot?: any): void {
if (prevProps.selectedFolder !== this.props.selectedFolder) {
this.setState({ filteredFolders: this.props.folders, blocks: this.getBlocks(this.props.folders) });
}
}
private getBlocks(folders: OfficeFolder[]): IBlock[] { private getBlocks(folders: OfficeFolder[]): IBlock[] {
const pendingFolders = folders const pendingFolders = folders
.filter((folder) => { .filter((folder) => {

View File

@ -0,0 +1,102 @@
@import "@Themes/constants.scss";
.root {
position: relative;
.input {
z-index: 1;
display: flex;
flex-direction: row;
align-items: center;
padding: 24px;
gap: 10px;
width: 100%;
height: 70px;
border: 1px solid var(--grey-medium);
&:disabled {
cursor: not-allowed;
}
&:focus {
~ .fake-placeholder {
transform: translateY(-35px);
}
}
&:not([data-value=""]) {
~ .fake-placeholder {
transform: translateY(-35px);
}
}
&[type="number"] {
&:focus {
~ .fake-placeholder {
transform: translateY(-35px);
}
}
&:not([data-value=""]) {
~ .fake-placeholder {
transform: translateY(-35px);
}
}
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For Chrome, Safari, and Opera */
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For IE 10+ */
&::-ms-inner-spin-button,
&::-ms-outer-spin-button {
display: none;
}
}
&:not([data-value=""]) {
~ .fake-placeholder {
transform: translateY(-35px);
}
}
~ .fake-placeholder {
z-index: 2;
top: 35%;
margin-left: 8px;
padding: 0 16px;
pointer-events: none;
position: absolute;
background: $white;
transition: transform 0.3s ease-in-out;
}
}
&[data-is-errored="true"] {
.input {
border: 1px solid var(--red-flash);
~ .fake-placeholder {
color: var(--red-flash);
}
}
}
.copy-icon {
cursor: pointer;
height: 24px;
width: 24px;
position: absolute;
top: 50%;
right: 24px;
transform: translate(0, -50%);
}
}

View File

@ -0,0 +1,56 @@
import React from "react";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import { ReactNode } from "react";
import CopyIcon from "@Assets/Icons/copy.svg";
import BaseField, { IProps as IBaseFieldProps } from "../BaseField";
import classes from "./classes.module.scss";
import classnames from "classnames";
import Image from "next/image";
export type IProps = IBaseFieldProps & {
canCopy?: boolean;
};
export default class DateField extends BaseField<IProps> {
constructor(props: IProps) {
super(props);
this.state = this.getDefaultState();
}
public override render(): ReactNode {
const value = this.state.value ?? "";
return (
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
<div className={classes["root"]} data-is-errored={this.hasError().toString()}>
<input
onChange={this.onChange}
data-value={value}
data-has-validation-errors={(this.state.validationError === null).toString()}
className={classnames(classes["input"], this.props.className)}
value={value}
onFocus={this.onFocus}
onBlur={this.onBlur}
name={this.props.name}
disabled={this.props.disabled}
type={"date"}
/>
<div className={classes["fake-placeholder"]}>
{this.props.placeholder} {!this.props.required && " (Facultatif)"}
</div>
{this.props.canCopy && (
<div className={classes["copy-icon"]} onClick={this.onCopyClick}>
<Image src={CopyIcon} alt="Copy icon" />
</div>
)}
</div>
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</Typography>
);
}
private onCopyClick = (): void => {
if (this.props.canCopy) {
navigator.clipboard.writeText(this.state.value ?? "");
}
};
}

View File

@ -7,6 +7,7 @@ import React, { FormEvent, ReactNode } from "react";
import Typography, { ITypo, ITypoColor } from "../../Typography"; import Typography, { ITypo, ITypoColor } from "../../Typography";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { NextRouter, useRouter } from "next/router";
type IProps = { type IProps = {
selectedOption?: IOption; selectedOption?: IOption;
@ -16,7 +17,7 @@ type IProps = {
placeholder?: string; placeholder?: string;
className?: string; className?: string;
name: string; name: string;
disabled: boolean; disabled?: boolean;
errors?: ValidationError; errors?: ValidationError;
}; };
@ -35,7 +36,11 @@ type IState = {
errors: ValidationError | null; errors: ValidationError | null;
}; };
export default class SelectField extends React.Component<IProps, IState> { type IPropsClass = IProps & {
router: NextRouter;
};
class SelectFieldClass extends React.Component<IPropsClass, IState> {
private contentRef = React.createRef<HTMLUListElement>(); private contentRef = React.createRef<HTMLUListElement>();
private rootRef = React.createRef<HTMLDivElement>(); private rootRef = React.createRef<HTMLDivElement>();
private removeOnresize = () => {}; private removeOnresize = () => {};
@ -44,7 +49,7 @@ export default class SelectField extends React.Component<IProps, IState> {
disabled: false, disabled: false,
}; };
constructor(props: IProps) { constructor(props: IPropsClass) {
super(props); super(props);
this.state = { this.state = {
isOpen: false, isOpen: false,
@ -64,7 +69,7 @@ export default class SelectField extends React.Component<IProps, IState> {
<div <div
className={classNames(classes["root"], this.props.className)} className={classNames(classes["root"], this.props.className)}
ref={this.rootRef} ref={this.rootRef}
data-disabled={this.props.disabled.toString()} data-disabled={this.props.disabled?.toString()}
data-errored={(this.state.errors !== null).toString()}> data-errored={(this.state.errors !== null).toString()}>
{selectedOption && <input type="text" defaultValue={selectedOption.value as string} name={this.props.name} hidden />} {selectedOption && <input type="text" defaultValue={selectedOption.value as string} name={this.props.name} hidden />}
<label <label
@ -116,6 +121,23 @@ export default class SelectField extends React.Component<IProps, IState> {
</div> </div>
); );
} }
public override componentDidMount(): void {
this.onResize();
this.removeOnresize = WindowStore.getInstance().onResize(() => this.onResize());
this.props.router.events.on("routeChangeStart", () => {
this.setState({
isOpen: false,
selectedOption: null,
listHeight: 0,
listWidth: 0,
});
});
}
public override componentWillUnmount() {
this.removeOnresize();
}
public override componentDidUpdate(prevProps: IProps) { public override componentDidUpdate(prevProps: IProps) {
if (this.props.errors !== prevProps.errors) { if (this.props.errors !== prevProps.errors) {
@ -140,15 +162,6 @@ export default class SelectField extends React.Component<IProps, IState> {
return null; return null;
} }
public override componentDidMount(): void {
this.onResize();
this.removeOnresize = WindowStore.getInstance().onResize(() => this.onResize());
}
public override componentWillUnmount() {
this.removeOnresize();
}
private onResize() { private onResize() {
let listHeight = 0; let listHeight = 0;
let listWidth = 0; let listWidth = 0;
@ -192,3 +205,8 @@ export default class SelectField extends React.Component<IProps, IState> {
); );
} }
} }
export default function SelectField(props: IProps) {
const router = useRouter();
return <SelectFieldClass {...props} router={router} />;
}

View File

@ -1,8 +1,8 @@
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import { IAppRule } from "@Front/Api/Entities/rule"; import { IAppRule } from "@Front/Api/Entities/rule";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
export enum RulesMode { export enum RulesMode {
OPTIONAL = "optional", OPTIONAL = "optional",
@ -10,16 +10,18 @@ export enum RulesMode {
} }
type IProps = { type IProps = {
isPage?: boolean;
mode: RulesMode; mode: RulesMode;
rules: IAppRule[]; rules: IAppRule[];
no?: boolean;
children: JSX.Element; children: JSX.Element;
isPage?: boolean;
}; };
export default function Rules(props: IProps) { export default function Rules(props: IProps) {
const router = useRouter(); const router = useRouter();
const [isShowing, setIsShowing] = React.useState(false);
const [hasJwt, setHasJwt] = React.useState(false);
const getShowValue = useCallback(() => { const getShowValue = useCallback(() => {
if (props.mode === RulesMode.NECESSARY) { if (props.mode === RulesMode.NECESSARY) {
return props.rules.every((rule) => JwtService.getInstance().hasRule(rule.name, rule.action)); return props.rules.every((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
@ -27,19 +29,18 @@ export default function Rules(props: IProps) {
return !!props.rules.find((rule) => JwtService.getInstance().hasRule(rule.name, rule.action)); return !!props.rules.find((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
}, [props.mode, props.rules]); }, [props.mode, props.rules]);
const show = getShowValue();
const [isShowing, setIsShowing] = React.useState(props.no ? !show : show);
useEffect(() => { useEffect(() => {
setIsShowing(props.no ? !show : show); if (!JwtService.getInstance().decodeJwt()) return;
}, [props.no, show]); setHasJwt(true);
setIsShowing(getShowValue());
}, [getShowValue, isShowing]);
if (!isShowing && props.isPage) { if (props.isPage && !isShowing) {
router.push(Module.getInstance().get().modules.pages.Home.props.path); router.push(Module.getInstance().get().modules.pages[404].props.path);
}
if (!JwtService.getInstance().decodeJwt() || !isShowing) {
return null; return null;
} }
if (!hasJwt || !isShowing) return null;
return props.children; return props.children;
} }

View File

@ -45,9 +45,9 @@ export default function CollaboratorInformations(props: IProps) {
setRoleModalOpened(false); setRoleModalOpened(false);
setSelectedOption({ setSelectedOption({
value: userSelected?.office_role ? userSelected?.office_role?.uid : userSelected?.role?.uid, value: userSelected?.office_role ? userSelected?.office_role?.uid : userSelected?.role?.uid,
label: userSelected?.office_role ? userSelected?.office_role?.name : userSelected?.role?.name!, label: userSelected?.office_role ? userSelected?.office_role?.name : "Utilisateur restreint",
}); });
}, [userSelected?.office_role, userSelected?.role?.name, userSelected?.role?.uid]); }, [userSelected?.office_role, userSelected?.role?.uid]);
const changeRole = useCallback(async () => { const changeRole = useCallback(async () => {
await Users.getInstance().put( await Users.getInstance().put(
@ -133,7 +133,7 @@ export default function CollaboratorInformations(props: IProps) {
setUserSelected(user); setUserSelected(user);
setSelectedOption({ setSelectedOption({
value: user?.office_role ? user?.office_role?.uid : user?.role?.uid, value: user?.office_role ? user?.office_role?.uid : user?.role?.uid,
label: user?.office_role ? user?.office_role?.name : user?.role?.name!, label: user?.office_role ? user?.office_role?.name : "Utilisateur restreint",
}); });
} }
@ -197,7 +197,6 @@ export default function CollaboratorInformations(props: IProps) {
})} })}
selectedOption={selectedOption!} selectedOption={selectedOption!}
onChange={handleRoleChange} onChange={handleRoleChange}
disabled={userSelected?.role?.name === "super-admin"}
/> />
</div> </div>
{userSelected?.role?.name !== "super-admin" && ( {userSelected?.role?.name !== "super-admin" && (

View File

@ -72,6 +72,9 @@
border: 1px solid var(--grey); border: 1px solid var(--grey);
.container-title { .container-title {
display: flex;
gap: 8px;
align-items: center;
} }
.documents { .documents {

View File

@ -19,6 +19,7 @@ import { useCallback, useEffect, useState } from "react";
import { MultiValue } from "react-select"; import { MultiValue } from "react-select";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Tooltip from "@Front/Components/DesignSystem/ToolTip";
type IProps = {}; type IProps = {};
export default function DeedTypesInformations(props: IProps) { export default function DeedTypesInformations(props: IProps) {
@ -30,6 +31,7 @@ export default function DeedTypesInformations(props: IProps) {
const [selectedDocuments, setSelectedDocuments] = useState<IOption[]>([]); const [selectedDocuments, setSelectedDocuments] = useState<IOption[]>([]);
const [isDeleteModalOpened, setIsDeleteModalOpened] = useState<boolean>(false); const [isDeleteModalOpened, setIsDeleteModalOpened] = useState<boolean>(false);
const [isSaveModalOpened, setIsSaveModalOpened] = useState<boolean>(false);
const openDeleteModal = useCallback(() => { const openDeleteModal = useCallback(() => {
setIsDeleteModalOpened(true); setIsDeleteModalOpened(true);
@ -39,6 +41,14 @@ export default function DeedTypesInformations(props: IProps) {
setIsDeleteModalOpened(false); setIsDeleteModalOpened(false);
}, []); }, []);
const openSaveModal = useCallback(() => {
setIsSaveModalOpened(true);
}, []);
const closeSaveModal = useCallback(() => {
setIsSaveModalOpened(false);
}, []);
const deleteDeedType = useCallback(async () => { const deleteDeedType = useCallback(async () => {
await DeedTypes.getInstance().put( await DeedTypes.getInstance().put(
deedTypeUid as string, deedTypeUid as string,
@ -81,14 +91,19 @@ export default function DeedTypesInformations(props: IProps) {
const onSubmitHandler = useCallback( const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => { async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
await DeedTypes.getInstance().put(deedTypeUid as string, { openSaveModal();
uid: deedTypeUid as string,
document_types: selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.value as string })),
});
}, },
[deedTypeUid, selectedDocuments], [openSaveModal],
); );
const saveDocumentTypes = useCallback(async () => {
await DeedTypes.getInstance().put(deedTypeUid as string, {
uid: deedTypeUid as string,
document_types: selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.value as string })),
});
closeSaveModal();
}, [closeSaveModal, deedTypeUid, selectedDocuments]);
const onDocumentChangeHandler = useCallback((values: MultiValue<IOption>) => { const onDocumentChangeHandler = useCallback((values: MultiValue<IOption>) => {
setSelectedDocuments(values as IOption[]); setSelectedDocuments(values as IOption[]);
}, []); }, []);
@ -137,7 +152,8 @@ export default function DeedTypesInformations(props: IProps) {
<div className={classes["documents-container"]}> <div className={classes["documents-container"]}>
<Form onSubmit={onSubmitHandler}> <Form onSubmit={onSubmitHandler}>
<div className={classes["container-title"]}> <div className={classes["container-title"]}>
<Typography typo={ITypo.P_SB_18}>Documents paramétrés</Typography> <Typography typo={ITypo.P_SB_18}>Sélectionner les documents associés à ce type d'acte</Typography>
<Tooltip text="Si vous ne trouvez pas le document que vous souhaitez dans la liste, cliquez sur « Modifier la liste des documents » pour créer ce type de document à la liste" />
</div> </div>
<div className={classes["documents"]}> <div className={classes["documents"]}>
<MultiSelect <MultiSelect
@ -171,6 +187,20 @@ export default function DeedTypesInformations(props: IProps) {
</Typography> </Typography>
</div> </div>
</Confirm> </Confirm>
<Confirm
isOpen={isSaveModalOpened}
onClose={closeSaveModal}
onAccept={saveDocumentTypes}
closeBtn
header={"Enregistrer les modifications ?"}
confirmText={"Enregistrer"}
cancelText={"Annuler"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Les documents seront associés à ce type d'acte.
</Typography>
</div>
</Confirm>
</div> </div>
</DefaultDeedTypesDashboard> </DefaultDeedTypesDashboard>
); );

View File

@ -1,6 +1,18 @@
@import "@Themes/constants.scss"; @import "@Themes/constants.scss";
.root { .root {
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
@media (max-width: $screen-l) {
flex-direction: column;
align-items: flex-start;
gap: 24px;
}
}
.document-infos { .document-infos {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;

View File

@ -1,3 +1,4 @@
import ChevronIcon from "@Assets/Icons/chevron.svg";
import PenICon from "@Assets/Icons/pen.svg"; import PenICon from "@Assets/Icons/pen.svg";
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
@ -10,6 +11,7 @@ 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 Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
export default function DocumentTypesInformations() { export default function DocumentTypesInformations() {
const router = useRouter(); const router = useRouter();
@ -33,8 +35,14 @@ export default function DocumentTypesInformations() {
return ( return (
<DefaultDocumentTypesDashboard mobileBackText={"Liste des collaborateurs"}> <DefaultDocumentTypesDashboard mobileBackText={"Liste des collaborateurs"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["folder-header"]}> <div className={classes["header"]}>
<Typography typo={ITypo.H1Bis}>Paramétrage des documents</Typography> <Typography typo={ITypo.H1Bis}>Paramétrage des listes de pièces</Typography>
<Link href={Module.getInstance().get().modules.pages.DeedTypes.props.path}>
<Button variant={EButtonVariant.LINE}>
Retour au paramétrage des types d'actes
<Image src={ChevronIcon} alt="Chevron" />
</Button>
</Link>
</div> </div>
<div className={classes["document-infos"]}> <div className={classes["document-infos"]}>
<div className={classes["left"]}> <div className={classes["left"]}>

View File

@ -9,7 +9,6 @@ import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import BackArrow from "@Front/Components/Elements/BackArrow"; import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard"; import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { TextField } from "@mui/material";
import { ECivility } from "le-coffre-resources/dist/Customer/Contact"; import { ECivility } from "le-coffre-resources/dist/Customer/Contact";
import { Customer, OfficeFolder } from "le-coffre-resources/dist/Notary"; import { Customer, OfficeFolder } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
@ -17,6 +16,7 @@ import { NextRouter, useRouter } from "next/router";
import BasePage from "../../Base"; import BasePage from "../../Base";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
enum ESelectedOption { enum ESelectedOption {
EXISTING_CUSTOMER = "existing_customer", EXISTING_CUSTOMER = "existing_customer",

View File

@ -34,6 +34,7 @@ type IState = {
inputArchivedDescripton: string; inputArchivedDescripton: string;
isValidateModalVisible: boolean; isValidateModalVisible: boolean;
hasValidateAnchoring: boolean; hasValidateAnchoring: boolean;
isVerifDeleteModalVisible: boolean;
}; };
class FolderInformationClass extends BasePage<IPropsClass, IState> { class FolderInformationClass extends BasePage<IPropsClass, IState> {
public constructor(props: IPropsClass) { public constructor(props: IPropsClass) {
@ -44,6 +45,7 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
inputArchivedDescripton: "", inputArchivedDescripton: "",
isValidateModalVisible: false, isValidateModalVisible: false,
hasValidateAnchoring: false, hasValidateAnchoring: false,
isVerifDeleteModalVisible: false,
}; };
this.onSelectedFolder = this.onSelectedFolder.bind(this); this.onSelectedFolder = this.onSelectedFolder.bind(this);
this.openArchivedModal = this.openArchivedModal.bind(this); this.openArchivedModal = this.openArchivedModal.bind(this);
@ -55,6 +57,8 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
this.closeModal = this.closeModal.bind(this); this.closeModal = this.closeModal.bind(this);
this.validateAnchoring = this.validateAnchoring.bind(this); this.validateAnchoring = this.validateAnchoring.bind(this);
this.openValidateModal = this.openValidateModal.bind(this); this.openValidateModal = this.openValidateModal.bind(this);
this.openVerifDeleteFolder = this.openVerifDeleteFolder.bind(this);
this.closeVerifDeleteFolder = this.closeVerifDeleteFolder.bind(this);
} }
// TODO: Message if the user has not created any folder yet // TODO: Message if the user has not created any folder yet
@ -109,7 +113,7 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
</Button> </Button>
)} )}
{!this.doesFolderHaveCustomer() && ( {!this.doesFolderHaveCustomer() && (
<span className={classes["delete-folder"]} onClick={this.deleteFolder}> <span className={classes["delete-folder"]} onClick={this.openVerifDeleteFolder}>
<Button variant={EButtonVariant.SECONDARY}>Supprimer le dossier</Button> <Button variant={EButtonVariant.SECONDARY}>Supprimer le dossier</Button>
</span> </span>
)} )}
@ -131,6 +135,18 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
onChange={this.onArchivedDescriptionInputChange} onChange={this.onArchivedDescriptionInputChange}
/> />
</Confirm> </Confirm>
<Confirm
isOpen={this.state.isVerifDeleteModalVisible}
onAccept={this.deleteFolder}
onClose={this.closeVerifDeleteFolder}
closeBtn
header={"Êtes-vous sûr de vouloir supprimer ce dossier ?"}
cancelText={"Annuler"}
confirmText={"Confirmer"}>
<div className={classes["modal-title"]}>
<Typography typo={ITypo.P_16}>Cette action sera irréversible.</Typography>
</div>
</Confirm>
</div> </div>
) : ( ) : (
<div className={classes["no-folder-selected"]}> <div className={classes["no-folder-selected"]}>
@ -187,6 +203,18 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
}); });
} }
public openVerifDeleteFolder() {
this.setState({
isVerifDeleteModalVisible: true,
});
}
public closeVerifDeleteFolder() {
this.setState({
isVerifDeleteModalVisible: false,
});
}
private closeModal() { private closeModal() {
this.setState({ this.setState({
isValidateModalVisible: false, isValidateModalVisible: false,

View File

@ -13,6 +13,7 @@ import { NextRouter, useRouter } from "next/router";
import BasePage from "../../Base"; import BasePage from "../../Base";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import DateField from "@Front/Components/DesignSystem/Form/DateField";
type IProps = {}; type IProps = {};
@ -43,6 +44,8 @@ class UpdateFolderMetadataClass extends BasePage<IPropsClass, IState> {
value: this.state.selectedFolder?.deed?.deed_type?.uid, value: this.state.selectedFolder?.deed?.deed_type?.uid,
} as IOption; } as IOption;
const openingDate = new Date(this.state.selectedFolder?.created_at ?? ""); const openingDate = new Date(this.state.selectedFolder?.created_at ?? "");
if (!this.state.selectedFolder?.created_at) return <></>;
const defaultValue = openingDate.toISOString().split("T")[0];
return ( return (
<DefaultNotaryDashboard title={"Ajouter client(s)"} onSelectedFolder={this.onSelectedFolder}> <DefaultNotaryDashboard title={"Ajouter client(s)"} onSelectedFolder={this.onSelectedFolder}>
<div className={classes["root"]}> <div className={classes["root"]}>
@ -60,7 +63,7 @@ class UpdateFolderMetadataClass extends BasePage<IPropsClass, IState> {
defaultValue={this.state.selectedFolder?.folder_number} defaultValue={this.state.selectedFolder?.folder_number}
/> />
<Select name="deed" options={[]} placeholder={"Type d'acte"} selectedOption={deedOption} disabled /> <Select name="deed" options={[]} placeholder={"Type d'acte"} selectedOption={deedOption} disabled />
<TextField placeholder="Ouverture du dossier" defaultValue={openingDate.toLocaleDateString("fr-FR")} disabled /> <DateField name="opening_date" placeholder="Ouverture du dossier" defaultValue={defaultValue} disabled />
</div> </div>
<div className={classes["button-container"]}> <div className={classes["button-container"]}>

View File

@ -49,6 +49,7 @@ export default class ClientSection extends React.Component<IProps, IState> {
key={customer.uid} key={customer.uid}
isOpened={this.state.openedCustomer === customer.uid} isOpened={this.state.openedCustomer === customer.uid}
onChange={this.changeUserFolder} onChange={this.changeUserFolder}
isArchived
/> />
); );
}); });

View File

@ -12,6 +12,8 @@ import { useCallback, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
type IProps = {}; type IProps = {};
export default function RolesCreate(props: IProps) { export default function RolesCreate(props: IProps) {
@ -61,35 +63,44 @@ export default function RolesCreate(props: IProps) {
}, [hasChanged, redirect]); }, [hasChanged, redirect]);
return ( return (
<DefaultRolesDashboard mobileBackText={"Liste des rôles"} hasBackArrow title="Créer un rôle"> <Rules
<div className={classes["root"]}> mode={RulesMode.NECESSARY}
<div className={classes["header"]}> rules={[
<Typography typo={ITypo.H1Bis}>Créer un rôle</Typography> {
action: AppRuleActions.create,
name: AppRuleNames.officeRoles,
},
]}>
<DefaultRolesDashboard mobileBackText={"Liste des rôles"} hasBackArrow title="Créer un rôle">
<div className={classes["root"]}>
<div className={classes["header"]}>
<Typography typo={ITypo.H1Bis}>Créer un rôle</Typography>
</div>
<Form onSubmit={onSubmitHandler} className={classes["form-container"]} onFieldChange={onFieldChange}>
<TextField name="name" placeholder="Nom du rôle" />
<div className={classes["buttons-container"]}>
<Button variant={EButtonVariant.GHOST} onClick={onCancel}>
Annuler
</Button>
<Button type="submit">Créer le rôle</Button>
</div>
</Form>
<Confirm
isOpen={isConfirmModalVisible}
onClose={closeConfirmModal}
onAccept={redirect}
closeBtn
header={"Êtes-vous sur de vouloir quitter sans enregistrer ?"}
cancelText={"Annuler"}
confirmText={"Quitter"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Si vous quittez, toutes les modifications que vous avez effectuées ne seront pas enregistrées.
</Typography>
</div>
</Confirm>
</div> </div>
<Form onSubmit={onSubmitHandler} className={classes["form-container"]} onFieldChange={onFieldChange}> </DefaultRolesDashboard>
<TextField name="name" placeholder="Nom du rôle" /> </Rules>
<div className={classes["buttons-container"]}>
<Button variant={EButtonVariant.GHOST} onClick={onCancel}>
Annuler
</Button>
<Button type="submit">Créer le rôle</Button>
</div>
</Form>
<Confirm
isOpen={isConfirmModalVisible}
onClose={closeConfirmModal}
onAccept={redirect}
closeBtn
header={"Êtes-vous sur de vouloir quitter sans enregistrer ?"}
cancelText={"Annuler"}
confirmText={"Quitter"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Si vous quittez, toutes les modifications que vous avez effectuées ne seront pas enregistrées.
</Typography>
</div>
</Confirm>
</div>
</DefaultRolesDashboard>
); );
} }

View File

@ -81,7 +81,11 @@ export default function UserInformations(props: IProps) {
useEffect(() => { useEffect(() => {
if (!userSelected) return; if (!userSelected) return;
setCurrentAppointment(userSelected?.appointment?.find((appointment) => appointment.status === EAppointmentStatus.OPEN && appointment.votes?.length != 0) ?? null); setCurrentAppointment(
userSelected?.appointment?.find(
(appointment) => appointment.status === EAppointmentStatus.OPEN && appointment.votes?.length != 0,
) ?? null,
);
}, [userSelected]); }, [userSelected]);
/** Functions for the admin modal */ /** Functions for the admin modal */
@ -271,7 +275,7 @@ export default function UserInformations(props: IProps) {
onChange={handleRoleChange} onChange={handleRoleChange}
selectedOption={{ selectedOption={{
value: userSelected?.office_role ? userSelected?.office_role?.uid : userSelected?.role?.uid, value: userSelected?.office_role ? userSelected?.office_role?.uid : userSelected?.role?.uid,
label: userSelected?.office_role ? userSelected?.office_role?.name : userSelected?.role?.label!, label: userSelected?.office_role ? userSelected?.office_role?.name : "Utilisateur restreint",
}} }}
/> />
</div> </div>
@ -282,7 +286,12 @@ export default function UserInformations(props: IProps) {
</div> </div>
<div className={classes["second-line"]}> <div className={classes["second-line"]}>
<Switch label="Admin de son office" checked={isAdminChecked} onChange={handleAdminChanged} /> <Switch label="Admin de son office" checked={isAdminChecked} onChange={handleAdminChanged} />
<Switch label="Super-admin LeCoffre.io" checked={isSuperAdminChecked} disabled={userHasVoted()} onChange={handleSuperAdminChanged} /> <Switch
label="Super-admin LeCoffre.io"
checked={isSuperAdminChecked}
disabled={userHasVoted()}
onChange={handleSuperAdminChanged}
/>
{currentAppointment && ( {currentAppointment && (
<div className={classes["votes-block"]}> <div className={classes["votes-block"]}>
<div className={classes["left"]}> <div className={classes["left"]}>

View File

@ -68,10 +68,8 @@ export default class JwtService {
} }
public hasRule(name: string, action: string) { public hasRule(name: string, action: string) {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); const token = this.decodeJwt();
if (!accessToken) return false; if (!token) return false;
const decodedToken = this.decodeJwt(); return token?.rules?.some((rule: string) => rule === `${action} ${name}`);
if (!decodedToken) return false;
return decodedToken?.rules?.some((rule: string) => rule === `${action} ${name}`);
} }
} }

View File

@ -22,22 +22,6 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(new URL("/login", request.url)); return NextResponse.redirect(new URL("/login", request.url));
} }
const requestUrlPath = request.nextUrl.pathname;
if (
requestUrlPath.startsWith("/collaborators") ||
requestUrlPath.startsWith("/deed-types") ||
requestUrlPath.startsWith("/customer") ||
requestUrlPath.startsWith("/offices") ||
requestUrlPath.startsWith("/roles") ||
requestUrlPath.startsWith("/users")
) {
if (userDecodedToken.role !== "admin" && userDecodedToken.role !== "super-admin")
return NextResponse.redirect(new URL("/404", request.url));
}
if ((requestUrlPath.startsWith("/my-account") || requestUrlPath.startsWith("/document-types")) && !userDecodedToken)
return NextResponse.redirect(new URL("/404", request.url));
if (requestUrlPath.startsWith("/client-dashboard") && !customerDecodedToken) return NextResponse.redirect(new URL("/404", request.url));
return NextResponse.next(); return NextResponse.next();
} }