Merge Staging in Preprod (#86)

This commit is contained in:
Arnaud D. Natali 2023-10-03 18:13:00 +02:00 committed by GitHub
commit 373db86456
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 546 additions and 340 deletions

40
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.85",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.89",
"next": "13.2.4",
"prettier": "^2.8.7",
"react": "18.2.0",
@ -394,9 +394,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.0.tgz",
"integrity": "sha512-zJmuCWj2VLBt4c25CfBIbMZLGLyhkvs7LznyVX5HfpzeocThgIj5XQK4L+g3U36mMcx8bPMhGyPpwCATamC4jQ==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz",
"integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==",
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
@ -461,9 +461,9 @@
}
},
"node_modules/@floating-ui/utils": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.4.tgz",
"integrity": "sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA=="
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.11",
@ -965,9 +965,9 @@
}
},
"node_modules/@rushstack/eslint-patch": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.0.tgz",
"integrity": "sha512-EF3948ckf3f5uPgYbQ6GhyA56Dmv8yg0+ir+BroRjwdxyZJsekhZzawOecC2rOTPCz173t7ZcR1HHZu0dZgOCw=="
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz",
"integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA=="
},
"node_modules/@swc/helpers": {
"version": "0.4.14",
@ -993,9 +993,9 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"node_modules/@types/prop-types": {
"version": "15.7.7",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.7.tgz",
"integrity": "sha512-FbtmBWCcSa2J4zL781Zf1p5YUBXQomPEcep9QZCfRfQgTxz3pJWiDFLebohZ9fFntX5ibzOkSsrJ0TEew8cAog=="
"version": "15.7.8",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz",
"integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ=="
},
"node_modules/@types/react": {
"version": "18.0.28",
@ -1507,9 +1507,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001541",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz",
"integrity": "sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==",
"version": "1.0.30001543",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001543.tgz",
"integrity": "sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA==",
"funding": [
{
"type": "opencollective",
@ -3361,7 +3361,7 @@
}
},
"node_modules/le-coffre-resources": {
"resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#0e1663716a698cc584a89e5e2a03b72113702d55",
"resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#a100398ef5c1984ba74cb1c8c182648315accc3e",
"license": "MIT",
"dependencies": {
"class-transformer": "^0.5.1",
@ -4067,9 +4067,9 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-select": {
"version": "5.7.5",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.5.tgz",
"integrity": "sha512-jgYZa2xgKP0DVn5GZk7tZwbRx7kaVz1VqU41S8z1KWmshRDhlrpKS0w80aS1RaK5bVIXpttgSou7XCjWw1ncKA==",
"version": "5.7.7",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.7.tgz",
"integrity": "sha512-HhashZZJDRlfF/AKj0a0Lnfs3sRdw/46VJIRd8IbB9/Ovr74+ZIwkAdSBjSPXsFMG+u72c5xShqwLSKIJllzqw==",
"dependencies": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.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.85",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.89",
"next": "13.2.4",
"prettier": "^2.8.7",
"react": "18.2.0",

View File

@ -15,5 +15,6 @@ export enum AppRuleNames {
officeFolders = "folders",
officeRoles = "office-roles",
deedTypes = "deed-types",
offices = "offices"
offices = "offices",
documents = "documents",
}

View File

@ -7,6 +7,7 @@ export interface IGetFoldersParams {
q?: {
select?: {};
where?: {};
orderBy?: {}[];
include?: {};
};
}

View File

@ -1,9 +1,15 @@
import { ContentType } from "@Front/Api/BaseApiService";
import BaseNotary from "../BaseNotary";
import { OfficeFolderAnchor } from "le-coffre-resources/dist/Notary";
export interface IGetAnchorsParams {
where?: {};
include?: {};
select?: {};
}
export default class OfficeFolderAnchors extends BaseNotary {
private static instance: OfficeFolderAnchors;
private readonly baseURl = this.namespaceUrl.concat("/anchors");
private readonly baseUrl = this.namespaceUrl.concat("/anchors");
private constructor() {
super();
@ -17,20 +23,34 @@ export default class OfficeFolderAnchors extends BaseNotary {
}
}
public async get(uid: string): Promise<any> {
const url = new URL(this.baseURl.concat(`/${uid}`));
public async get(q?: IGetAnchorsParams): Promise<OfficeFolderAnchor[]> {
const url = new URL(this.baseUrl);
if (q) {
const query = { q };
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
}
try {
return await this.getRequest<any>(url);
return await this.getRequest<OfficeFolderAnchor[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async post(uid: string): Promise<any> {
const url = new URL(this.baseURl.concat(`/${uid}`));
public async getByUid(uid: string): Promise<OfficeFolderAnchor> {
const url = new URL(this.baseUrl.concat(`/${uid}`));
try {
return await this.postRequest<any>(url, {});
return await this.getRequest<OfficeFolderAnchor>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async post(uid: string): Promise<OfficeFolderAnchor> {
const url = new URL(this.baseUrl.concat(`/${uid}`));
try {
return await this.postRequest<OfficeFolderAnchor>(url, {});
} catch (err) {
this.onError(err);
return Promise.reject(err);
@ -38,7 +58,7 @@ export default class OfficeFolderAnchors extends BaseNotary {
}
public async download(uid: string): Promise<any> {
const url = new URL(this.baseURl.concat(`/download/${uid}`));
const url = new URL(this.baseUrl.concat(`/download/${uid}`));
try {
return await this.getRequest<any>(url, undefined, ContentType.PDF, `${uid}.pdf`);
} catch (err) {

View File

@ -1,4 +1,4 @@
import { Appointment } from "le-coffre-resources/dist/SuperAdmin";
import { Appointment, Vote } from "le-coffre-resources/dist/SuperAdmin";
import BaseSuperAdmin from "../BaseSuperAdmin";
@ -18,6 +18,9 @@ export type LiveVote = {
appointment: Appointment;
};
export type IDeleteVotesParams = {
uid: Vote["uid"];
};
export default class LiveVotes extends BaseSuperAdmin {
private static instance: LiveVotes;
private readonly baseURl = this.namespaceUrl.concat("/live-votes");
@ -46,4 +49,17 @@ export default class LiveVotes extends BaseSuperAdmin {
return Promise.reject(err);
}
}
/**
* @description : Delete a vote
*/
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);
}
}
}

View File

@ -1,44 +0,0 @@
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);
}
}
}

View File

@ -71,7 +71,7 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
return (
<Confirm
isOpen={this.state.isAddDocumentModalVisible}
onClose={() => {}}
onClose={this.props.onClose!}
onAccept={this.onAccept}
closeBtn
header={"Ajouter un document"}

View File

@ -10,6 +10,8 @@ import Typography, { ITypo } from "../../Typography";
import WarningBadge from "../../WarningBadge";
import classes from "./classes.module.scss";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
type IProps = {
folderUid: string;
@ -60,8 +62,11 @@ class DocumentNotaryClass extends React.Component<IPropsClass, IState> {
} else {
const archivedFilesLength = documentFiles.filter((file) => file.archived_at).length;
const documentFileLength = documentFiles.length - archivedFilesLength;
if(this.props.document.document_status === EDocumentStatus.ASKED || this.props.document.document_status === EDocumentStatus.REFUSED){
return 'Aucun document déposé';
if (
this.props.document.document_status === EDocumentStatus.ASKED ||
this.props.document.document_status === EDocumentStatus.REFUSED
) {
return "Aucun document déposé";
}
return `${documentFileLength} documents déposés`;
@ -91,7 +96,18 @@ class DocumentNotaryClass extends React.Component<IPropsClass, IState> {
case EDocumentStatus.DEPOSITED:
return <WarningBadge />;
default:
return <Image src={TrashIcon} alt="trash icon" className={classes["trash"]} onClick={this.onOpenDeletionModal} />;
return (
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.delete,
name: AppRuleNames.documents,
},
]}>
<Image src={TrashIcon} alt="trash icon" className={classes["trash"]} onClick={this.onOpenDeletionModal} />
</Rules>
);
}
}

View File

@ -17,9 +17,11 @@ export default class FilePreview extends React.Component<IProps, IState> {
if (this.props.fileName) type = this.props.fileName.split(".").pop();
return (
<div className={classes["root"]}>
{!this.props.href && (
<div className={classes["loader"]}>
<Loader />
</div>
)}
{this.props.href && (
<>

View File

@ -8,10 +8,33 @@ import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
import { usePathname } from "next/navigation";
import Notifications from "@Front/Api/LeCoffreApi/Notary/Notifications/Notifications";
import Toasts from "@Front/Stores/Toasts";
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
export default function Navigation() {
const pathname = usePathname();
const getAnchoringStatus = useCallback(async () => {
const anchors = await OfficeFolderAnchors.getInstance().get({
where: {
status: {
not: "VERIFIED_ON_CHAIN",
},
},
include: {
folder: true,
},
});
try {
for (const anchor of anchors) {
await OfficeFolderAnchors.getInstance().getByUid(anchor.folder?.uid as string);
}
} catch (e) {
console.log(e);
}
}, []);
const getNotifications = useCallback(async () => {
//await getAnchoringStatus();
const notifications = await Notifications.getInstance().get({
where: {
read: false,
@ -27,8 +50,9 @@ export default function Navigation() {
}, []);
useEffect(() => {
getAnchoringStatus();
getNotifications();
}, [pathname, getNotifications]);
}, [pathname, getNotifications, getAnchoringStatus]);
return (
<div className={classes["root"]}>

View File

@ -13,7 +13,7 @@ export default function LogOut() {
const disconnect = async() => {
await UserStore.instance.disconnect();
router.push(`https://qual-connexion.idnot.fr/user/auth/logout?post_logout_redirect_uri=${variables.FRONT_APP_HOST}/login`);
router.push(`https://qual-connexion.idnot.fr/user/auth/logout?sourceURL=${variables.FRONT_APP_HOST}`);
};
return (

View File

@ -108,10 +108,12 @@ class ToastElementClass extends React.Component<IPropsClass, IState> {
}, 200);
}
private handleClick(e: React.MouseEvent) {
private async handleClick(e: React.MouseEvent) {
console.log('redirectUrl', this.props.toast.redirectUrl);
if (this.props.toast.redirectUrl) {
this.props.router.push(this.props.toast.redirectUrl);
this.onClose(e);
await this.props.router.push(this.props.toast.redirectUrl);
this.props.router.reload();
}
}
}

View File

@ -8,7 +8,7 @@ type IProps = {};
type IState = {
toastList: IToast[];
};
export default class ToastsContainer extends React.Component<IProps, IState> {
export class ToastsContainerClass extends React.Component<IProps, IState> {
private removeOnChange = () => {};
public constructor(props: IProps) {
@ -44,3 +44,7 @@ export default class ToastsContainer extends React.Component<IProps, IState> {
this.setState({ toastList });
}
}
export default function ToastsContainer(props: IProps) {
return <ToastsContainerClass {...props}/>;
};

View File

@ -0,0 +1,11 @@
.add-document-form-container {
display: flex;
flex-direction: column;
gap: 24px;
.radiobox-container {
> :not(:last-child) {
margin-bottom: 16px;
}
}
}

View File

@ -0,0 +1,159 @@
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import classes from "./classes.module.scss";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import { ChangeEvent, useCallback, useEffect, useState } from "react";
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import { DocumentType, OfficeFolder } from "le-coffre-resources/dist/Notary";
import Deeds from "@Front/Api/LeCoffreApi/Notary/Deeds/Deeds";
import MultiSelect from "@Front/Components/DesignSystem/MultiSelect";
import { IOption } from "@Front/Components/DesignSystem/Form/SelectField";
import { MultiValue } from "react-select";
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
type IProps = {
isCreateDocumentModalVisible: boolean;
closeModal: () => void;
folder: OfficeFolder;
};
export default function ParameterDocuments(props: IProps) {
const [visibleDescription, setVisibleDescription] = useState<string>("");
const [documentName, setDocumentName] = useState<string>("");
const [addOrEditDocument, setAddOrEditDocument] = useState<"add" | "edit">("edit");
const [selectedDocuments, setSelectedDocuments] = useState<IOption[]>([]);
const [formattedOptions, setFormattedOptions] = useState<IOption[]>([]);
const getAvailableDocuments = useCallback(async () => {
const documents = await DocumentTypes.getInstance().get({});
const formattedOptions: IOption[] = documents
.filter((document) => {
return !props.folder.deed?.document_types?.some((documentType) => documentType.uid === document.uid);
})
.map((document) => {
return {
label: document.name,
value: document.uid,
};
});
setFormattedOptions(formattedOptions);
}, [props.folder.deed?.document_types]);
const onVisibleDescriptionChange = (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
setVisibleDescription(event.target.value);
};
const onDocumentNameChange = (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
setDocumentName(event.target.value);
};
const onDocumentChangeHandler = useCallback((values: MultiValue<IOption>) => {
setSelectedDocuments(values as IOption[]);
}, []);
const handleClose = useCallback(() => {
setFormattedOptions([]);
setSelectedDocuments([]);
setAddOrEditDocument("edit");
setVisibleDescription("");
setDocumentName("");
props.closeModal();
}, [props]);
const addDocument = useCallback(async () => {
if (addOrEditDocument === "add") {
try {
const documentType = await DocumentTypes.getInstance().post({
name: documentName,
private_description: visibleDescription,
office: {
uid: props.folder.office!.uid!,
},
public_description: visibleDescription,
});
const oldDocumentsType = props.folder.deed?.document_types!;
await Deeds.getInstance().put(props.folder.deed?.uid!, {
document_types: [...oldDocumentsType, documentType],
});
//await this.loadData();
handleClose();
} catch (e) {
console.error(e);
}
} else {
try {
const oldDocumentsType = props.folder.deed?.document_types!;
await Deeds.getInstance().put(props.folder.deed?.uid!, {
document_types: [
...oldDocumentsType,
...selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.value as string })),
],
});
//await this.loadData();
handleClose();
} catch (e) {
console.error(e);
}
}
}, [addOrEditDocument, documentName, handleClose, props, selectedDocuments, visibleDescription]);
const selectEditMode = () => {
setAddOrEditDocument("edit");
};
const selectAddMode = () => {
setAddOrEditDocument("add");
};
useEffect(() => {
getAvailableDocuments();
}, [getAvailableDocuments, props.folder]);
return (
<Confirm
isOpen={props.isCreateDocumentModalVisible}
onClose={handleClose}
onAccept={addDocument}
closeBtn
header={"Ajouter des documents demandables"}
cancelText={"Annuler"}
confirmText={"Ajouter"}>
<div className={classes["add-document-form-container"]}>
<div className={classes["radiobox-container"]}>
<RadioBox name="document" onChange={selectEditMode} checked={addOrEditDocument === "edit"} value={"existing client"}>
<Typography typo={ITypo.P_ERR_18}>Document existant</Typography>
</RadioBox>
<RadioBox name="document" onChange={selectAddMode} checked={addOrEditDocument === "add"} value={"new client"}>
<Typography typo={ITypo.P_ERR_18}>Créer un document</Typography>
</RadioBox>
</div>
{addOrEditDocument === "add" && (
<>
<TextField name="document_name" placeholder="Nom du document à ajouter" onChange={onDocumentNameChange} />
<TextAreaField
name="description"
placeholder="Description visible par le client"
onChange={onVisibleDescriptionChange}
/>
</>
)}
{addOrEditDocument === "edit" && (
<MultiSelect
options={formattedOptions}
placeholder="Cliquez pour sélectionner des documents"
onChange={onDocumentChangeHandler}
defaultValue={selectedDocuments}
/>
)}
</div>
</Confirm>
);
}

View File

@ -37,10 +37,4 @@
.buttons-container {
margin-top: 32px;
}
.add-document-form-container {
display: flex;
flex-direction: column;
gap: 24px;
}
}

View File

@ -1,15 +1,10 @@
import PlusIcon from "@Assets/Icons/plus.svg";
import Deeds from "@Front/Api/LeCoffreApi/Notary/Deeds/Deeds";
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Form from "@Front/Components/DesignSystem/Form";
import { IOption } from "@Front/Components/DesignSystem/Form/SelectField";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
@ -20,6 +15,7 @@ import React from "react";
import BasePage from "../../Base";
import classes from "./classes.module.scss";
import ParameterDocuments from "./ParameterDocuments";
type IProps = {};
type IPropsClass = IProps & {
@ -29,8 +25,6 @@ type IPropsClass = IProps & {
};
type IState = {
isCreateDocumentModalVisible: boolean;
documentName: string;
visibleDescription: string;
documentTypes: IOption[];
folder: OfficeFolder | null;
};
@ -41,8 +35,6 @@ class AskDocumentsClass extends BasePage<IPropsClass, IState> {
this.state = {
isCreateDocumentModalVisible: false,
documentName: "",
visibleDescription: "",
documentTypes: [],
folder: null,
};
@ -50,11 +42,6 @@ class AskDocumentsClass extends BasePage<IPropsClass, IState> {
this.onFormSubmit = this.onFormSubmit.bind(this);
this.closeModal = this.closeModal.bind(this);
this.openModal = this.openModal.bind(this);
this.cancel = this.cancel.bind(this);
this.onVisibleDescriptionChange = this.onVisibleDescriptionChange.bind(this);
this.onDocumentNameChange = this.onDocumentNameChange.bind(this);
this.addDocument = this.addDocument.bind(this);
this.canAddDocument = this.canAddDocument.bind(this);
}
public override render(): JSX.Element {
@ -101,25 +88,14 @@ class AskDocumentsClass extends BasePage<IPropsClass, IState> {
</div>
</div>
</Form>
<Confirm
isOpen={this.state.isCreateDocumentModalVisible}
onClose={this.closeModal}
onAccept={this.addDocument}
canConfirm={this.canAddDocument()}
closeBtn
header={"Créer un type de document"}
cancelText={"Annuler"}
confirmText={"Ajouter"}>
<div className={classes["add-document-form-container"]}>
<TextField name="document_name" placeholder="Nom du document à ajouter" onChange={this.onDocumentNameChange} />
<TextAreaField
name="description"
placeholder="Description visible par le client"
onChange={this.onVisibleDescriptionChange}
</div>
{this.state.folder && (
<ParameterDocuments
folder={this.state.folder}
closeModal={this.closeModal}
isCreateDocumentModalVisible={this.state.isCreateDocumentModalVisible}
/>
</div>
</Confirm>
</div>
)}
</DefaultNotaryDashboard>
);
}
@ -128,6 +104,8 @@ class AskDocumentsClass extends BasePage<IPropsClass, IState> {
this.loadData();
}
private cancel() {}
private async loadData() {
try {
const folder = await Folders.getInstance().getByUid(this.props.folderUid, {
@ -186,72 +164,16 @@ class AskDocumentsClass extends BasePage<IPropsClass, IState> {
return documentTypesOptions;
}
private canAddDocument() {
if (this.state.documentName === "" || this.state.visibleDescription === "") {
return false;
}
return true;
}
private async addDocument() {
try {
const documentType = await DocumentTypes.getInstance().post({
name: this.state.documentName,
private_description: this.state.visibleDescription,
office: {
uid: this.state.folder?.office!.uid!,
},
public_description: this.state.visibleDescription,
});
const oldDocumentsType = this.state.folder?.deed?.document_types!;
await Deeds.getInstance().put(this.state.folder?.deed?.uid!, {
document_types: [...oldDocumentsType, documentType],
});
await this.loadData();
this.setState({
isCreateDocumentModalVisible: false,
documentName: "",
visibleDescription: "",
});
} catch (e) {
console.error(e);
}
}
private onVisibleDescriptionChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) {
this.setState({
visibleDescription: e.target.value,
});
}
private onDocumentNameChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) {
this.setState({
documentName: e.target.value,
});
}
private cancel() {
this.setState({
visibleDescription: "",
documentName: "",
});
}
private openModal() {
this.setState({
isCreateDocumentModalVisible: true,
visibleDescription: "",
documentName: "",
});
}
private closeModal() {
this.loadData();
this.setState({
isCreateDocumentModalVisible: false,
visibleDescription: "",
documentName: "",
});
}

View File

@ -99,3 +99,15 @@
}
}
}
.loader-container {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
height: 100%;
.loader {
width: 40px;
height: 40px;
}
}

View File

@ -13,7 +13,7 @@ import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import Link from "next/link";
import { NextRouter, useRouter } from "next/router";
import { ChangeEvent } from "react";
import { ChangeEvent, useCallback, useEffect, useState } from "react";
import Image from "next/image";
import ValidateAnchoringGif from "@Front/Assets/images/validate_anchoring.gif";
@ -21,36 +21,44 @@ import BasePage from "../../Base";
import classes from "./classes.module.scss";
import ClientSection from "./ClientSection";
import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Loader from "@Front/Components/DesignSystem/Loader";
enum AnchorStatus {
"VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN",
"ANCHORING" = "ANCHORING",
"NOT_ANCHORED" = "NOT_ANCHORED",
}
type IProps = {};
type IPropsClass = IProps & {
router: NextRouter;
selectedFolderUid: string;
isAnchored: AnchorStatus;
isLoading: boolean;
selectedFolder: OfficeFolder | null;
getAnchoringStatus: () => Promise<void>;
};
type IState = {
selectedFolder: OfficeFolder | null;
isArchivedModalOpen: boolean;
inputArchivedDescripton: string;
isValidateModalVisible: boolean;
hasValidateAnchoring: boolean;
isVerifDeleteModalVisible: boolean;
isAnchored: boolean | null;
isPreventArchiveModalOpen: boolean;
};
class FolderInformationClass extends BasePage<IPropsClass, IState> {
public constructor(props: IPropsClass) {
super(props);
this.state = {
selectedFolder: null,
isArchivedModalOpen: false,
inputArchivedDescripton: "",
isValidateModalVisible: false,
hasValidateAnchoring: false,
isVerifDeleteModalVisible: false,
isAnchored: null,
isPreventArchiveModalOpen: false,
};
this.onSelectedFolder = this.onSelectedFolder.bind(this);
this.openArchivedModal = this.openArchivedModal.bind(this);
this.closeArchivedModal = this.closeArchivedModal.bind(this);
this.onArchivedModalAccepted = this.onArchivedModalAccepted.bind(this);
@ -62,7 +70,7 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
this.openValidateModal = this.openValidateModal.bind(this);
this.openVerifDeleteFolder = this.openVerifDeleteFolder.bind(this);
this.closeVerifDeleteFolder = this.closeVerifDeleteFolder.bind(this);
this.verifyAnchorStatus = this.verifyAnchorStatus.bind(this);
this.closePreventArchiveModal = this.closePreventArchiveModal.bind(this);
}
// TODO: Message if the user has not created any folder yet
@ -72,13 +80,10 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
.get()
.modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", this.props.selectedFolderUid);
return (
<DefaultNotaryDashboard
title={"Dossier"}
onSelectedFolder={this.onSelectedFolder}
isArchived={false}
mobileBackText="Retour aux dossiers">
<DefaultNotaryDashboard title={"Dossier"} isArchived={false} mobileBackText="Retour aux dossiers">
{!this.props.isLoading && (
<div className={classes["root"]}>
{this.state.selectedFolder ? (
{this.props.selectedFolder ? (
<div className={classes["folder-informations"]}>
<div className={classes["folder-header"]}>
<div className={classes["header"]}>
@ -91,9 +96,15 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
</Button>
</Link>
</div>
<FolderBoxInformation folder={this.state.selectedFolder} type={EFolderBoxInformationType.INFORMATIONS} />
<FolderBoxInformation
folder={this.props.selectedFolder}
type={EFolderBoxInformationType.INFORMATIONS}
/>
<div className={classes["second-box"]}>
<FolderBoxInformation folder={this.state.selectedFolder} type={EFolderBoxInformationType.DESCRIPTION} />
<FolderBoxInformation
folder={this.props.selectedFolder}
type={EFolderBoxInformationType.DESCRIPTION}
/>
</div>
<div className={classes["progress-bar"]}>
<QuantityProgressBar
@ -102,26 +113,32 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
currentNumber={this.getCompletionNumber()}
/>
</div>
{this.doesFolderHaveCustomer() && <ClientSection folder={this.state.selectedFolder} />}
{this.doesFolderHaveCustomer() && <ClientSection folder={this.props.selectedFolder} />}
</div>
{!this.doesFolderHaveCustomer() && <ClientSection folder={this.state.selectedFolder} />}
{!this.doesFolderHaveCustomer() && <ClientSection folder={this.props.selectedFolder} />}
<div className={classes["button-container"]}>
<Button variant={EButtonVariant.GHOST} onClick={this.openArchivedModal}>
Archiver le dossier
</Button>
{this.everyDocumentValidated() && (
{this.everyDocumentValidated() && !this.props.isLoading && (
<>
{this.state.isAnchored === null && (
{this.props.isAnchored === AnchorStatus.NOT_ANCHORED && (
<Button variant={EButtonVariant.PRIMARY} onClick={this.openValidateModal}>
Ancrer le dossier
</Button>
)}
{this.state.isAnchored === true && (
{this.props.isAnchored === AnchorStatus.ANCHORING && (
<Button variant={EButtonVariant.PRIMARY} disabled>
Ancrage en cours...&nbsp;&nbsp;
<Loader />
</Button>
)}
{this.props.isAnchored === AnchorStatus.VERIFIED_ON_CHAIN && (
<Button
variant={EButtonVariant.PRIMARY}
onClick={() => this.downloadAnchoringProof(this.state.selectedFolder?.uid)}>
onClick={() => this.downloadAnchoringProof(this.props.selectedFolder?.uid)}>
Télécharger la preuve d'ancrage
</Button>
)}
@ -174,6 +191,30 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
</div>
)}
</div>
)}
{this.props.isLoading && (
<div className={classes["loader-container"]}>
<div className={classes["loader"]}>
<Loader />
</div>
</div>
)}
<Confirm
isOpen={this.state.isPreventArchiveModalOpen}
onAccept={this.closePreventArchiveModal}
onClose={this.closePreventArchiveModal}
closeBtn
header={"Vous devez valider et certifier un dossier avant de pouvoir l'archiver"}
cancelText={"Annuler"}
confirmText={"J'ai compris"}>
<div className={classes["modal-title"]}>
<Typography typo={ITypo.P_16}>
Pour valider un dossier, toutes les pièces envoyées par vos clients doivent être validées (vert). Si certains
documents sont en attente (orange), alors, veuillez les valider ou les refuser et veillez à ce qu'aucun document
ne soit encore en demandé au client (gris)
</Typography>
</div>
</Confirm>
<Confirm
isOpen={this.state.isValidateModalVisible}
onClose={this.closeModal}
@ -213,31 +254,13 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
</DefaultNotaryDashboard>
);
}
public override async componentDidMount() {
const selectedFolder = await this.getFolder();
this.setState(
{
selectedFolder,
},
() => {
this.verifyAnchorStatus();
},
);
private closePreventArchiveModal() {
this.setState({
isPreventArchiveModalOpen: false,
});
}
private async verifyAnchorStatus() {
if (!this.state.selectedFolder || !this.state.selectedFolder.uid) return;
try {
const anchorStatus = await OfficeFolderAnchors.getInstance().get(this.state.selectedFolder.uid!);
this.setState({
isAnchored: anchorStatus.status === "VERIFIED_ON_CHAIN",
});
} catch (e) {
this.setState({
isAnchored: null,
});
}
}
public openVerifDeleteFolder() {
this.setState({
isVerifDeleteModalVisible: true,
@ -291,8 +314,9 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
}
private async anchorFolder() {
if (!this.state.selectedFolder?.uid) return;
return await OfficeFolderAnchors.getInstance().post(this.state.selectedFolder.uid);
if (!this.props.selectedFolder?.uid) return;
await OfficeFolderAnchors.getInstance().post(this.props.selectedFolder.uid);
this.props.getAnchoringStatus();
}
private async downloadAnchoringProof(uid?: string) {
@ -305,7 +329,7 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
a.style.display = "none";
a.href = url;
// the filename you want
a.download = `anchoring_proof_${this.state.selectedFolder?.folder_number}_${this.state.selectedFolder?.name}.pdf`;
a.download = `anchoring_proof_${this.props.selectedFolder?.folder_number}_${this.props.selectedFolder?.name}.pdf`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
@ -315,21 +339,21 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
}
private everyDocumentValidated(): boolean {
if (!this.state.selectedFolder?.documents) return false;
if (!this.props.selectedFolder?.documents) return false;
return (
this.state.selectedFolder?.documents?.length >= 1 &&
this.state.selectedFolder?.documents.every((document) => document.document_status === EDocumentStatus.VALIDATED)
this.props.selectedFolder?.documents?.length >= 1 &&
this.props.selectedFolder?.documents.every((document) => document.document_status === EDocumentStatus.VALIDATED)
);
}
private async deleteFolder() {
if (!this.state.selectedFolder?.uid) return;
await Folders.getInstance().delete(this.state.selectedFolder.uid);
if (!this.props.selectedFolder?.uid) return;
await Folders.getInstance().delete(this.props.selectedFolder.uid);
this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path);
}
private getCompletionNumber() {
const documents = this.state.selectedFolder?.documents;
const documents = this.props.selectedFolder?.documents;
if (!documents) return 0;
const totalDocuments = documents.length;
const refusedDocuments = documents.filter((document) => document.document_status === EDocumentStatus.REFUSED).length ?? 0;
@ -343,16 +367,16 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
}
private doesFolderHaveCustomer(): boolean {
if (!this.state.selectedFolder?.customers) return false;
return this.state.selectedFolder?.customers!.length > 0;
}
private onSelectedFolder(folder: OfficeFolder): void {
this.setState({ selectedFolder: folder });
if (!this.props.selectedFolder?.customers) return false;
return this.props.selectedFolder?.customers!.length > 0;
}
private openArchivedModal(): void {
if (this.everyDocumentValidated() && this.props.isAnchored) {
this.setState({ isArchivedModalOpen: true });
} else {
this.setState({ isPreventArchiveModalOpen: true });
}
}
private closeArchivedModal(): void {
@ -364,15 +388,37 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
}
private async onArchivedModalAccepted() {
if (!this.state.selectedFolder) return;
const ressourceFolder = OfficeFolder.hydrate<OfficeFolder>(this.state.selectedFolder);
if (!this.props.selectedFolder) return;
const ressourceFolder = OfficeFolder.hydrate<OfficeFolder>(this.props.selectedFolder);
ressourceFolder.archived_description = this.state.inputArchivedDescripton;
await Folders.getInstance().archive(this.state.selectedFolder.uid ?? "", ressourceFolder);
await Folders.getInstance().archive(this.props.selectedFolder.uid ?? "", ressourceFolder);
this.closeArchivedModal();
this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path);
}
}
private async getFolder(): Promise<OfficeFolder> {
export default function FolderInformation(props: IProps) {
const router = useRouter();
const [isAnchored, setIsAnchored] = useState<AnchorStatus>(AnchorStatus.NOT_ANCHORED);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [selectedFolder, setSelectedFolder] = useState<OfficeFolder | null>(null);
let { folderUid } = router.query;
folderUid = folderUid as string;
const getAnchoringStatus = useCallback(async () => {
if (!folderUid) return;
try {
const anchorStatus = await OfficeFolderAnchors.getInstance().getByUid(folderUid as string);
setIsAnchored(anchorStatus.status === "VERIFIED_ON_CHAIN" ? AnchorStatus.VERIFIED_ON_CHAIN : AnchorStatus.ANCHORING);
} catch (e) {
setIsAnchored(AnchorStatus.NOT_ANCHORED);
}
}, [folderUid]);
const getFolder = useCallback(async () => {
if (!folderUid) return;
setIsLoading(true);
const query = {
q: {
deed: { include: { deed_type: true } },
@ -402,14 +448,29 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
},
};
const folder = await Folders.getInstance().getByUid(this.props.selectedFolderUid, query);
return folder;
}
const folder = await Folders.getInstance().getByUid(folderUid as string, query);
if (folder) {
setSelectedFolder(folder);
getAnchoringStatus();
}
export default function FolderInformation(props: IProps) {
const router = useRouter();
let { folderUid } = router.query;
folderUid = folderUid as string;
return <FolderInformationClass {...props} selectedFolderUid={folderUid} router={router} />;
setIsLoading(false);
}, [folderUid, getAnchoringStatus]);
useEffect(() => {
setIsLoading(true);
getFolder();
}, [getFolder]);
return (
<FolderInformationClass
{...props}
selectedFolderUid={folderUid}
router={router}
isAnchored={isAnchored}
isLoading={isLoading}
selectedFolder={selectedFolder}
getAnchoringStatus={getAnchoringStatus}
/>
);
}

View File

@ -9,7 +9,8 @@ import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module";
import User, { OfficeFolder } from "le-coffre-resources/dist/Notary";
import User from "le-coffre-resources/dist/Notary/User";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import Link from "next/link";
import { NextRouter, useRouter } from "next/router";

View File

@ -29,6 +29,11 @@ export default function SelectFolder() {
},
},
},
orderBy: [
{
created_at: "desc",
},
],
},
});
setFolders(folders);

View File

@ -3,7 +3,6 @@ import WarningIcon from "@Assets/images/warning.png";
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";
@ -224,7 +223,7 @@ export default function UserInformations(props: IProps) {
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 LiveVotes.getInstance().delete({ uid: vote.uid });
await getUser();
}, [currentAppointment, getUser]);

View File

@ -47,7 +47,7 @@ const MyApp = (({
instance.BACK_API_HOST = backApiHost ?? "api.stg.lecoffre.smart-chain.fr";
instance.BACK_API_ROOT_URL = backApiRootUrl ?? "/api";
instance.BACK_API_VERSION = backApiVersion ?? "/v1";
instance.FRONT_APP_HOST = frontAppHost ?? "app.stg.lecoffre.smart-chain.fr";
instance.FRONT_APP_HOST = frontAppHost ?? "https://app.stg.lecoffre.smart-chain.fr";
instance.IDNOT_BASE_URL = idNotBaseUrl ?? "https://qual-connexion.idnot.fr";
instance.IDNOT_AUTHORIZE_ENDPOINT = idNotAuthorizeEndpoint ?? "/IdPOAuth2/authorize/idnot_idp_v1";
instance.IDNOT_CLIENT_ID = idNotClientId ?? "4501646203F3EF67";