Refacto/folder information ❇️ (#174)
This commit is contained in:
parent
ab8cecc075
commit
a666e1054e
86
src/front/Components/DesignSystem/Alert/classes.module.scss
Normal file
86
src/front/Components/DesignSystem/Alert/classes.module.scss
Normal file
@ -0,0 +1,86 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
width: fit-content;
|
||||
display: inline-flex;
|
||||
padding: var(--spacing-2, 16px);
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg, 24px);
|
||||
|
||||
border-radius: var(--alerts-radius, 0px);
|
||||
border: 1px solid var(--alerts-info-border);
|
||||
background: var(--alerts-info-background);
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md, 16px);
|
||||
|
||||
.text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
gap: var(--spacing-md, 16px);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
padding: var(--spacing-1, 8px);
|
||||
align-items: center;
|
||||
|
||||
border-radius: var(--alerts-badge-radius, 360px);
|
||||
border: 1px solid var(--alerts-badge-border, rgba(0, 0, 0, 0));
|
||||
background: var(--alerts-badge-background, #fff);
|
||||
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
|
||||
stroke: var(--alerts-badge-contrast-info);
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: var(--alerts-error-border);
|
||||
background: var(--alerts-error-background);
|
||||
|
||||
.icon svg {
|
||||
stroke: var(--alerts-badge-contrast-error);
|
||||
}
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-color: var(--alerts-warning-border);
|
||||
background: var(--alerts-warning-background);
|
||||
|
||||
.icon svg {
|
||||
stroke: var(--alerts-badge-contrast-warning);
|
||||
}
|
||||
}
|
||||
|
||||
&.success {
|
||||
border-color: var(--alerts-success-border);
|
||||
background: var(--alerts-success-background);
|
||||
|
||||
.icon svg {
|
||||
stroke: var(--alerts-badge-contrast-success);
|
||||
}
|
||||
}
|
||||
|
||||
&.neutral {
|
||||
border-color: var(--alerts-neutral-border);
|
||||
background: var(--alerts-neutral-background);
|
||||
|
||||
.icon svg {
|
||||
stroke: var(--alerts-badge-contrast-neutral);
|
||||
}
|
||||
}
|
||||
}
|
80
src/front/Components/DesignSystem/Alert/index.tsx
Normal file
80
src/front/Components/DesignSystem/Alert/index.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import React from "react";
|
||||
import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant, IButtonProps } from "../Button";
|
||||
import classNames from "classnames";
|
||||
import IconButton from "../IconButton";
|
||||
import useOpenable from "@Front/Hooks/useOpenable";
|
||||
|
||||
type IProps = {
|
||||
variant: EAlertVariant;
|
||||
title: string;
|
||||
description: string;
|
||||
icon?: React.ReactNode;
|
||||
firstButton?: IButtonProps;
|
||||
secondButton?: IButtonProps;
|
||||
closeButton?: boolean;
|
||||
};
|
||||
|
||||
export enum EAlertVariant {
|
||||
INFO = "info",
|
||||
SUCCESS = "success",
|
||||
WARNING = "warning",
|
||||
ERROR = "error",
|
||||
NEUTRAL = "neutral",
|
||||
}
|
||||
|
||||
const variantButtonMap: Record<EAlertVariant, EButtonVariant> = {
|
||||
[EAlertVariant.INFO]: EButtonVariant.PRIMARY,
|
||||
[EAlertVariant.SUCCESS]: EButtonVariant.SUCCESS,
|
||||
[EAlertVariant.WARNING]: EButtonVariant.WARNING,
|
||||
[EAlertVariant.ERROR]: EButtonVariant.ERROR,
|
||||
[EAlertVariant.NEUTRAL]: EButtonVariant.NEUTRAL,
|
||||
};
|
||||
|
||||
export default function Alert(props: IProps) {
|
||||
const { isOpen, close } = useOpenable({ defaultOpen: true });
|
||||
const { variant = EAlertVariant.INFO, title, description, firstButton, secondButton, closeButton, icon } = props;
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className={classNames(classes["root"], classes[variant])}>
|
||||
<span className={classes["icon"]}>{icon ?? <InformationCircleIcon />}</span>
|
||||
<div className={classes["content"]}>
|
||||
<div className={classes["text-container"]}>
|
||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TEXT_MD_light} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||
{description}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className={classes["button-container"]}>
|
||||
{firstButton && (
|
||||
<Button
|
||||
{...firstButton}
|
||||
size={firstButton.size ?? EButtonSize.MD}
|
||||
variant={firstButton.variant ?? variantButtonMap[variant]}
|
||||
styletype={firstButton.styletype ?? EButtonstyletype.OUTLINED}>
|
||||
{firstButton.children}
|
||||
</Button>
|
||||
)}
|
||||
{secondButton && (
|
||||
<Button
|
||||
{...secondButton}
|
||||
size={secondButton.size ?? EButtonSize.MD}
|
||||
variant={secondButton.variant ?? variantButtonMap[variant]}
|
||||
styletype={secondButton.styletype ?? EButtonstyletype.OUTLINED}>
|
||||
{secondButton.children}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{closeButton && <IconButton onClick={close} icon={<XMarkIcon />} />}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -25,7 +25,7 @@ export enum EButtonstyletype {
|
||||
TEXT = "text",
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
export type IButtonProps = {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined;
|
||||
children?: React.ReactNode;
|
||||
variant?: EButtonVariant;
|
||||
@ -40,7 +40,7 @@ type IProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function Button(props: IProps) {
|
||||
export default function Button(props: IButtonProps) {
|
||||
let {
|
||||
variant = EButtonVariant.PRIMARY,
|
||||
size = EButtonSize.LG,
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
percentage: number;
|
||||
};
|
||||
|
||||
export default function CircleProgress(props: IProps) {
|
||||
const { percentage } = props;
|
||||
|
||||
@ -27,13 +27,14 @@ export default function CircleProgress(props: IProps) {
|
||||
}, [percentage]);
|
||||
|
||||
useEffect(() => {
|
||||
setAnimatedProgress(0); // Reset progress
|
||||
requestRef.current = requestAnimationFrame(animate);
|
||||
return () => {
|
||||
if (requestRef.current) {
|
||||
cancelAnimationFrame(requestRef.current);
|
||||
}
|
||||
};
|
||||
}, [animate, percentage]);
|
||||
}, [percentage, animate]);
|
||||
|
||||
const radius = 11;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
@ -12,7 +12,7 @@
|
||||
.content {
|
||||
position: fixed;
|
||||
max-width: 600px;
|
||||
max-height: 85vh;
|
||||
max-height: 75vh;
|
||||
border-radius: var(--modal-radius, 0px);
|
||||
background: var(--modal-background, #fff);
|
||||
box-shadow: 0px 4px 18px 0px rgba(0, 0, 0, 0.15);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import classNames from "classnames";
|
||||
import classes from "./classes.module.scss";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "../Button";
|
||||
import React from "react";
|
||||
import Typography, { ETypo } from "../Typography";
|
||||
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import Button, { EButtonstyletype, EButtonVariant, IButtonProps } from "../Button";
|
||||
import IconButton, { EIconButtonVariant } from "../IconButton";
|
||||
import Typography, { ETypo } from "../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
className?: string;
|
||||
@ -13,18 +13,8 @@ type IProps = {
|
||||
onClose?: () => void;
|
||||
children?: React.ReactNode;
|
||||
title?: string;
|
||||
firstButton?: {
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
rightIcon?: React.ReactNode;
|
||||
leftIcon?: React.ReactNode;
|
||||
};
|
||||
secondButton?: {
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
rightIcon?: React.ReactNode;
|
||||
leftIcon?: React.ReactNode;
|
||||
};
|
||||
firstButton?: IButtonProps;
|
||||
secondButton?: IButtonProps;
|
||||
fullwidth?: boolean;
|
||||
fullscreen?: boolean;
|
||||
};
|
||||
@ -54,22 +44,18 @@ export default function Modal(props: IProps) {
|
||||
<div className={classes["footer"]}>
|
||||
{firstButton && (
|
||||
<Button
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
styletype={EButtonstyletype.OUTLINED}
|
||||
leftIcon={firstButton.leftIcon}
|
||||
rightIcon={firstButton.rightIcon}
|
||||
onClick={firstButton.onClick}>
|
||||
{firstButton.text}
|
||||
{...firstButton}
|
||||
variant={firstButton.variant ?? EButtonVariant.PRIMARY}
|
||||
styletype={firstButton.styletype ?? EButtonstyletype.OUTLINED}>
|
||||
{firstButton.children}
|
||||
</Button>
|
||||
)}
|
||||
{secondButton && (
|
||||
<Button
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
styletype={EButtonstyletype.CONTAINED}
|
||||
leftIcon={secondButton.leftIcon}
|
||||
rightIcon={secondButton.rightIcon}
|
||||
onClick={secondButton.onClick}>
|
||||
{secondButton.text}
|
||||
{...secondButton}
|
||||
variant={secondButton.variant ?? EButtonVariant.PRIMARY}
|
||||
styletype={secondButton.styletype ?? EButtonstyletype.CONTAINED}>
|
||||
{secondButton.children}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,22 +1,23 @@
|
||||
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import Newsletter from "@Front/Components/DesignSystem/Newsletter";
|
||||
import Table from "@Front/Components/DesignSystem/Table";
|
||||
import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import NumberPicker from "@Front/Components/Elements/NumberPicker";
|
||||
import Tabs from "@Front/Components/Elements/Tabs";
|
||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||
import useOpenable from "@Front/Hooks/useOpenable";
|
||||
import { ArrowLongLeftIcon, ArrowLongRightIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import useOpenable from "@Front/Hooks/useOpenable";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
import NumberPicker from "@Front/Components/Elements/NumberPicker";
|
||||
|
||||
export default function DesignSystem() {
|
||||
const { isOpen, open, close } = useOpenable();
|
||||
@ -85,8 +86,8 @@ export default function DesignSystem() {
|
||||
isOpen={isOpen}
|
||||
onClose={close}
|
||||
title={"Modal"}
|
||||
firstButton={{ text: "Annuler", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
|
||||
secondButton={{ text: "Confirmer", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}>
|
||||
firstButton={{ children: "Annuler", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
|
||||
secondButton={{ children: "Confirmer", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}>
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR}>Modal Content</Typography>
|
||||
</Modal>
|
||||
|
||||
@ -704,6 +705,64 @@ export default function DesignSystem() {
|
||||
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.INFO} />
|
||||
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.INFO} disabled />
|
||||
</div>
|
||||
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Alerts</Typography>
|
||||
<Alert
|
||||
title="Alert line which displays the main function or reason of the alert."
|
||||
description="Description"
|
||||
firstButton={{
|
||||
children: "Button",
|
||||
leftIcon: <ArrowLongLeftIcon />,
|
||||
rightIcon: <ArrowLongRightIcon />,
|
||||
}}
|
||||
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
|
||||
variant={EAlertVariant.INFO}
|
||||
closeButton
|
||||
/>
|
||||
<Alert
|
||||
title="Alert line which displays the main function or reason of the alert."
|
||||
description="Description"
|
||||
firstButton={{
|
||||
children: "Button",
|
||||
leftIcon: <ArrowLongLeftIcon />,
|
||||
rightIcon: <ArrowLongRightIcon />,
|
||||
}}
|
||||
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
|
||||
variant={EAlertVariant.ERROR}
|
||||
/>
|
||||
<Alert
|
||||
title="Alert line which displays the main function or reason of the alert."
|
||||
description="Description"
|
||||
firstButton={{
|
||||
children: "Button",
|
||||
leftIcon: <ArrowLongLeftIcon />,
|
||||
rightIcon: <ArrowLongRightIcon />,
|
||||
}}
|
||||
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
|
||||
variant={EAlertVariant.WARNING}
|
||||
/>
|
||||
<Alert
|
||||
title="Alert line which displays the main function or reason of the alert."
|
||||
description="Description"
|
||||
firstButton={{
|
||||
children: "Button",
|
||||
leftIcon: <ArrowLongLeftIcon />,
|
||||
rightIcon: <ArrowLongRightIcon />,
|
||||
}}
|
||||
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
|
||||
variant={EAlertVariant.SUCCESS}
|
||||
/>
|
||||
<Alert
|
||||
title="Alert line which displays the main function or reason of the alert."
|
||||
description="Description"
|
||||
firstButton={{
|
||||
children: "Button",
|
||||
leftIcon: <ArrowLongLeftIcon />,
|
||||
rightIcon: <ArrowLongRightIcon />,
|
||||
}}
|
||||
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
|
||||
variant={EAlertVariant.NEUTRAL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DefaultTemplate>
|
||||
|
@ -0,0 +1,24 @@
|
||||
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
|
||||
import { EButtonstyletype } from "@Front/Components/DesignSystem/Button";
|
||||
import { LockClosedIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
type IProps = {
|
||||
onAnchor: () => void;
|
||||
};
|
||||
|
||||
export default function AnchoringAlertInfo(props: IProps) {
|
||||
const { onAnchor } = props;
|
||||
return (
|
||||
<Alert
|
||||
title="Validation et Certification du Dossier"
|
||||
description="Votre dossier est désormais complet à 100%. Vous pouvez maintenant procéder à la validation et à l'ancrage des documents dans la blockchain. Cette étape garantit la sécurité et l'authenticité de vos documents."
|
||||
firstButton={{
|
||||
children: "Ancrer et certifier",
|
||||
styletype: EButtonstyletype.CONTAINED,
|
||||
rightIcon: <LockClosedIcon />,
|
||||
onClick: onAnchor,
|
||||
}}
|
||||
variant={EAlertVariant.INFO}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
|
||||
import { EButtonstyletype } from "@Front/Components/DesignSystem/Button";
|
||||
import { ArrowDownOnSquareIcon, CheckIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
type IProps = {
|
||||
onDownloadAnchoringProof: () => void;
|
||||
onArchive: () => void;
|
||||
};
|
||||
|
||||
export default function AnchoringAlertSuccess(props: IProps) {
|
||||
const { onDownloadAnchoringProof, onArchive } = props;
|
||||
return (
|
||||
<Alert
|
||||
title="Félicitations ! Dossier ancré avec succès"
|
||||
description="Votre dossier a été validé et ancré dans la blockchain. Vous pouvez maintenant télécharger la preuve d'ancrage pour vos archives."
|
||||
firstButton={{
|
||||
children: "Télécharger la preuve d'ancrage",
|
||||
styletype: EButtonstyletype.CONTAINED,
|
||||
rightIcon: <ArrowDownOnSquareIcon />,
|
||||
onClick: onDownloadAnchoringProof,
|
||||
}}
|
||||
secondButton={{
|
||||
children: "Archiver le dossier",
|
||||
onClick: onArchive,
|
||||
}}
|
||||
variant={EAlertVariant.SUCCESS}
|
||||
icon={<CheckIcon />}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
.anchoring {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
.validate-gif {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
|
||||
import ValidateAnchoringGif from "@Front/Assets/images/validate_anchoring.gif";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import Image from "next/image";
|
||||
import React, { useCallback, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
onClose?: () => void;
|
||||
folderUid: string;
|
||||
onAnchorSuccess?: () => void;
|
||||
};
|
||||
|
||||
export default function AnchoringModal(props: IProps) {
|
||||
const { isOpen, onClose, folderUid, onAnchorSuccess } = props;
|
||||
const [isAnchoring, setIsAnchoring] = useState(false);
|
||||
|
||||
const anchor = useCallback(() => {
|
||||
setIsAnchoring(true);
|
||||
OfficeFolderAnchors.getInstance()
|
||||
.post(folderUid)
|
||||
.then(onAnchorSuccess)
|
||||
.catch((e) => console.warn(e))
|
||||
}, [folderUid, onAnchorSuccess]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={"Êtes-vous sûr de vouloir certifier et ancrer ce dossier ?"}
|
||||
firstButton={!isAnchoring ? { children: "Non, Annuler", onClick: onClose } : undefined}
|
||||
secondButton={!isAnchoring ? { children: "Oui, certifier et ancrer", onClick: anchor } : undefined}>
|
||||
{!isAnchoring ? (
|
||||
<Typography typo={ETypo.TEXT_MD_light}>
|
||||
La certification et l'ancrage de ce dossier dans la blockchain sont des actions définitives et garantiront la sécurité
|
||||
et l'authenticité de tous les documents. Veuillez confirmer que vous souhaitez continuer.
|
||||
</Typography>
|
||||
) : (
|
||||
<div className={classes["anchoring"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_light}>
|
||||
Vos documents sont en train d'être ancrés dans la blockchain. Cela peut prendre quelques instants. Merci de votre
|
||||
patience.
|
||||
</Typography>
|
||||
<Image src={ValidateAnchoringGif} alt="Anchoring animation" className={classes["validate-gif"]} />
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useCallback } from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
onClose?: () => void;
|
||||
folderUid: string;
|
||||
};
|
||||
|
||||
export default function ArchiveModal(props: IProps) {
|
||||
const { isOpen, onClose, folderUid } = props;
|
||||
const router = useRouter();
|
||||
|
||||
const archive = useCallback(() => {
|
||||
if (!folderUid) return;
|
||||
const description = (document.querySelector("textarea[name='archived_description']") as HTMLTextAreaElement).value ?? "";
|
||||
|
||||
Folders.getInstance()
|
||||
.archive(folderUid, description)
|
||||
.then(onClose)
|
||||
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
|
||||
.catch((e) => {
|
||||
console.warn(e);
|
||||
});
|
||||
}, [folderUid, onClose, router]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={"Voulez-vous archiver ce dossier ?"}
|
||||
firstButton={{ children: "Annuler", onClick: onClose }}
|
||||
secondButton={{ children: "Archiver le dossier", onClick: archive }}>
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_light}>
|
||||
Archiver ce dossier le déplacera dans la section des dossiers archivés. Vous pouvez ajouter une note de dossier avant
|
||||
d'archiver si vous le souhaitez.
|
||||
</Typography>
|
||||
<TextAreaField name="archived_description" placeholder="Ecrire une note" />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -28,8 +28,8 @@ export default function DeleteCustomerModal(props: IProps) {
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={"Êtes-vous sûr de vouloir supprimer ce client du dossier ?"}
|
||||
firstButton={{ text: "Annuler", onClick: onClose }}
|
||||
secondButton={{ text: "Supprimer le client", onClick: onDelete }}>
|
||||
firstButton={{ children: "Annuler", onClick: onClose }}
|
||||
secondButton={{ children: "Supprimer le client", onClick: onDelete }}>
|
||||
<Typography typo={ETypo.TEXT_MD_light}>
|
||||
Cette action retirera le client de ce dossier. Vous ne pourrez plus récupérer les informations associées à ce client dans ce
|
||||
dossier une fois supprimées.
|
||||
|
@ -5,15 +5,17 @@ import useOpenable from "@Front/Hooks/useOpenable";
|
||||
import { PencilSquareIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
import { ICustomer } from "..";
|
||||
import { AnchorStatus } from "../..";
|
||||
import classes from "./classes.module.scss";
|
||||
import DeleteCustomerModal from "./DeleteCustomerModal";
|
||||
|
||||
type IProps = {
|
||||
customer: ICustomer;
|
||||
anchorStatus: AnchorStatus;
|
||||
};
|
||||
|
||||
export default function ClientBox(props: IProps) {
|
||||
const { customer } = props;
|
||||
const { customer, anchorStatus } = props;
|
||||
|
||||
const { isOpen, open, close } = useOpenable();
|
||||
|
||||
@ -23,7 +25,9 @@ export default function ClientBox(props: IProps) {
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_PRIMARY_500}>
|
||||
{customer.contact?.last_name}
|
||||
</Typography>
|
||||
<IconButton variant={EIconButtonVariant.NEUTRAL} icon={<PencilSquareIcon />} />
|
||||
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||
<IconButton variant={EIconButtonVariant.NEUTRAL} icon={<PencilSquareIcon />} />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||
@ -46,19 +50,23 @@ export default function ClientBox(props: IProps) {
|
||||
Note client
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||
TODO
|
||||
{customer.notes?.[0]?.content ?? "_"}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={classes["delete-button"]}
|
||||
variant={EButtonVariant.ERROR}
|
||||
styletype={EButtonstyletype.TEXT}
|
||||
rightIcon={<TrashIcon />}
|
||||
onClick={open}>
|
||||
Supprimer le client
|
||||
</Button>
|
||||
<DeleteCustomerModal isOpen={isOpen} onClose={close} customerUid={customer.uid ?? ""} onDeleteSuccess={() => {}} />
|
||||
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||
<>
|
||||
<Button
|
||||
className={classes["delete-button"]}
|
||||
variant={EButtonVariant.ERROR}
|
||||
styletype={EButtonstyletype.TEXT}
|
||||
rightIcon={<TrashIcon />}
|
||||
onClick={open}>
|
||||
Supprimer le client
|
||||
</Button>
|
||||
<DeleteCustomerModal isOpen={isOpen} onClose={close} customerUid={customer.uid ?? ""} onDeleteSuccess={() => {}} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ export default function DeleteAskedDocumentModal(props: IProps) {
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={"Êtes-vous sûr de vouloir supprimer cette demande de document ?"}
|
||||
firstButton={{ text: "Annuler", onClick: onClose }}
|
||||
secondButton={{ text: "Supprimer la demande", onClick: onDelete }}>
|
||||
firstButton={{ children: "Annuler", onClick: onClose }}
|
||||
secondButton={{ children: "Supprimer la demande", onClick: onDelete }}>
|
||||
<Typography typo={ETypo.TEXT_MD_light}>Cette action annulera la demande du document en cours.</Typography>
|
||||
</Modal>
|
||||
);
|
||||
|
@ -0,0 +1,20 @@
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import { File } from "le-coffre-resources/dist/Customer";
|
||||
import React from "react";
|
||||
|
||||
type IProps = {
|
||||
file: File;
|
||||
url: string;
|
||||
isOpen: boolean;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export default function FilePreviewModal(props: IProps) {
|
||||
const { isOpen, onClose, file, url } = props;
|
||||
|
||||
return (
|
||||
<Modal key={file.uid} isOpen={isOpen} onClose={onClose} fullscreen>
|
||||
<object data={url} type={file.mimetype} width="100%" height="800px" />
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import Files from "@Front/Api/LeCoffreApi/Notary/Files/Files";
|
||||
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
||||
import IconButton from "@Front/Components/DesignSystem/IconButton";
|
||||
import Table from "@Front/Components/DesignSystem/Table";
|
||||
@ -6,16 +7,16 @@ import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag"
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import useOpenable from "@Front/Hooks/useOpenable";
|
||||
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { Document } from "le-coffre-resources/dist/Customer";
|
||||
import { Document, File } from "le-coffre-resources/dist/Customer";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal";
|
||||
import FilePreviewModal from "./FilePreviewModal";
|
||||
|
||||
type IProps = {
|
||||
documents: Document[];
|
||||
totalOfDocumentTypes: number;
|
||||
};
|
||||
|
||||
const header: readonly IHead[] = [
|
||||
@ -38,9 +39,11 @@ const header: readonly IHead[] = [
|
||||
];
|
||||
|
||||
export default function DocumentTables(props: IProps) {
|
||||
const { documents: documentsProps, totalOfDocumentTypes } = props;
|
||||
const { documents: documentsProps } = props;
|
||||
const [documents, setDocuments] = useState<Document[]>(documentsProps);
|
||||
const [documentUid, setDocumentUid] = useState<string | null>(null);
|
||||
const previewModal = useOpenable();
|
||||
const [file, setFile] = useState<{ file: File; blob: Blob } | null>(null);
|
||||
|
||||
const deleteAskedOocumentModal = useOpenable();
|
||||
|
||||
@ -57,7 +60,37 @@ export default function DocumentTables(props: IProps) {
|
||||
[deleteAskedOocumentModal],
|
||||
);
|
||||
|
||||
const askDocuments: IRowProps[] = useMemo(
|
||||
const onPreview = useCallback(
|
||||
(document: Document) => {
|
||||
const file = document.files?.[0];
|
||||
if (!file || !file?.uid) return;
|
||||
return Files.getInstance()
|
||||
.download(file.uid)
|
||||
.then((blob) => setFile({ file, blob }))
|
||||
.then(() => previewModal.open())
|
||||
.catch((e) => console.warn(e));
|
||||
},
|
||||
[previewModal],
|
||||
);
|
||||
|
||||
const onDownload = useCallback((doc: Document) => {
|
||||
const file = doc.files?.[0];
|
||||
if (!file || !file?.uid) return;
|
||||
|
||||
return Files.getInstance()
|
||||
.download(file.uid)
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = file.file_name ?? "file";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch((e) => console.warn(e));
|
||||
}, []);
|
||||
|
||||
const askedDocuments: IRowProps[] = useMemo(
|
||||
() =>
|
||||
documents
|
||||
.map((document) => {
|
||||
@ -88,11 +121,11 @@ export default function DocumentTables(props: IProps) {
|
||||
<Tag color={ETagColor.WARNING} variant={ETagVariant.SEMI_BOLD} label={document.document_status.toUpperCase()} />
|
||||
),
|
||||
created_at: document.created_at,
|
||||
actions: <IconButton icon={<EyeIcon />} />,
|
||||
actions: <IconButton onClick={() => onPreview(document)} icon={<EyeIcon />} />,
|
||||
};
|
||||
})
|
||||
.filter((document) => document !== null) as IRowProps[],
|
||||
[documents],
|
||||
[documents, onPreview],
|
||||
);
|
||||
|
||||
const validatedDocuments: IRowProps[] = useMemo(
|
||||
@ -109,14 +142,14 @@ export default function DocumentTables(props: IProps) {
|
||||
created_at: document.created_at,
|
||||
actions: (
|
||||
<div className={classes["actions"]}>
|
||||
<IconButton icon={<EyeIcon />} />
|
||||
<IconButton icon={<ArrowDownTrayIcon />} />
|
||||
<IconButton onClick={() => onPreview(document)} icon={<EyeIcon />} />
|
||||
<IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
})
|
||||
.filter((document) => document !== null) as IRowProps[],
|
||||
[documents],
|
||||
[documents, onDownload, onPreview],
|
||||
);
|
||||
|
||||
const refusedDocuments: IRowProps[] = useMemo(
|
||||
@ -138,11 +171,11 @@ export default function DocumentTables(props: IProps) {
|
||||
[documents],
|
||||
);
|
||||
|
||||
const progressValidated = useMemo(() => {
|
||||
if (totalOfDocumentTypes === 0) return 100;
|
||||
if (validatedDocuments.length === 0) return 0;
|
||||
return (validatedDocuments.length / totalOfDocumentTypes) * 100;
|
||||
}, [validatedDocuments, totalOfDocumentTypes]);
|
||||
const progress = useMemo(() => {
|
||||
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length + refusedDocuments.length;
|
||||
if (total === 0) return 0;
|
||||
return (validatedDocuments.length / total) * 100;
|
||||
}, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
@ -150,9 +183,9 @@ export default function DocumentTables(props: IProps) {
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||
Documents
|
||||
</Typography>
|
||||
<CircleProgress percentage={progressValidated} />
|
||||
<CircleProgress percentage={progress} />
|
||||
</div>
|
||||
<Table header={header} rows={askDocuments} />
|
||||
<Table header={header} rows={askedDocuments} />
|
||||
{toValidateDocuments.length > 0 && <Table header={header} rows={toValidateDocuments} />}
|
||||
{validatedDocuments.length > 0 && <Table header={header} rows={validatedDocuments} />}
|
||||
{refusedDocuments.length > 0 && <Table header={header} rows={refusedDocuments} />}
|
||||
@ -164,6 +197,14 @@ export default function DocumentTables(props: IProps) {
|
||||
documentUid={documentUid}
|
||||
/>
|
||||
)}
|
||||
{file && (
|
||||
<FilePreviewModal
|
||||
isOpen={previewModal.isOpen}
|
||||
onClose={previewModal.close}
|
||||
file={file.file}
|
||||
url={URL.createObjectURL(file.blob)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -47,8 +47,6 @@ export default function ClientView(props: IProps) {
|
||||
|
||||
const doesCustomerHaveDocument = useMemo(() => customer.documents && customer.documents.length > 0, [customer]);
|
||||
|
||||
const totalOfDocumentTypes = useMemo(() => folder.deed?.document_types?.length ?? 0, [folder]);
|
||||
|
||||
return (
|
||||
<section className={classes["root"]}>
|
||||
<div className={classes["tab-container"]}>
|
||||
@ -70,22 +68,20 @@ export default function ClientView(props: IProps) {
|
||||
</div>
|
||||
<div className={classes["content"]}>
|
||||
<div className={classes["client-box"]}>
|
||||
<ClientBox customer={customer} />
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.AskDocument.props.path.replace("[folderUid]", folder.uid ?? "")
|
||||
.replace("[customerUid]", customer.uid ?? "")}>
|
||||
<Button rightIcon={<DocumentIcon />} variant={EButtonVariant.PRIMARY} fullwidth>
|
||||
Demander un document
|
||||
</Button>
|
||||
</Link>
|
||||
<ClientBox customer={customer} anchorStatus={anchorStatus} />
|
||||
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.AskDocument.props.path.replace("[folderUid]", folder.uid ?? "")
|
||||
.replace("[customerUid]", customer.uid ?? "")}>
|
||||
<Button rightIcon={<DocumentIcon />} variant={EButtonVariant.PRIMARY} fullwidth>
|
||||
Demander un document
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
{doesCustomerHaveDocument ? (
|
||||
<DocumentTables documents={customer.documents ?? []} totalOfDocumentTypes={totalOfDocumentTypes} />
|
||||
) : (
|
||||
<NoDocument />
|
||||
)}
|
||||
{doesCustomerHaveDocument ? <DocumentTables documents={customer.documents ?? []}/> : <NoDocument />}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@ -0,0 +1,47 @@
|
||||
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
onClose?: () => void;
|
||||
folder: OfficeFolder;
|
||||
};
|
||||
|
||||
export default function DownloadAnchoringProofModal(props: IProps) {
|
||||
const { isOpen, onClose, folder } = props;
|
||||
|
||||
const downloadAnchoringProof = useCallback(async () => {
|
||||
if (!folder?.uid) return;
|
||||
try {
|
||||
const file = await OfficeFolderAnchors.getInstance().download(folder.uid);
|
||||
const url = window.URL.createObjectURL(file);
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = url;
|
||||
a.download = `anchoring_proof_${folder?.folder_number}_${folder?.name}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
onClose?.();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}, [folder?.folder_number, folder?.name, folder.uid, onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={"Félicitations ! Dossier ancré avec succès"}
|
||||
firstButton={{ children: "Fermer", onClick: onClose }}
|
||||
secondButton={{ children: "Télécharger la preuve d’ancrage", onClick: downloadAnchoringProof }}>
|
||||
<Typography typo={ETypo.TEXT_MD_light}>
|
||||
Votre dossier a été validé et ancré dans la blockchain. Vous pouvez maintenant télécharger la preuve d'ancrage pour vos
|
||||
archives.
|
||||
</Typography>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -5,31 +5,20 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
|
||||
import Module from "@Front/Config/Module";
|
||||
import { ArchiveBoxIcon, PencilSquareIcon, UserGroupIcon } from "@heroicons/react/24/outline";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
|
||||
import Link from "next/link";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import { AnchorStatus } from "..";
|
||||
|
||||
type IProps = {
|
||||
folder: OfficeFolder | null;
|
||||
progress: number;
|
||||
onArchive: () => void;
|
||||
anchorStatus: AnchorStatus;
|
||||
};
|
||||
|
||||
export default function InformationSection(props: IProps) {
|
||||
const { folder } = props;
|
||||
const getCompletionNumber = useCallback(() => {
|
||||
const documents = folder?.documents;
|
||||
if (!documents) return 0;
|
||||
const totalDocuments = documents.length;
|
||||
const refusedDocuments = documents.filter((document) => document.document_status === EDocumentStatus.REFUSED).length ?? 0;
|
||||
const askedDocuments =
|
||||
documents.filter(
|
||||
(document) => document.document_status === EDocumentStatus.ASKED || document.document_status === EDocumentStatus.DEPOSITED,
|
||||
).length ?? 0;
|
||||
const depositedDocuments = totalDocuments - askedDocuments - refusedDocuments;
|
||||
const percentage = (depositedDocuments / totalDocuments) * 100;
|
||||
return isNaN(percentage) ? 0 : percentage;
|
||||
}, [folder]);
|
||||
const { folder, progress, onArchive, anchorStatus } = props;
|
||||
|
||||
return (
|
||||
<section className={classes["root"]}>
|
||||
@ -51,26 +40,38 @@ export default function InformationSection(props: IProps) {
|
||||
|
||||
<div className={classes["info-box2"]}>
|
||||
<div className={classes["progress-container"]}>
|
||||
<CircleProgress percentage={getCompletionNumber()} />
|
||||
<CircleProgress percentage={progress} />
|
||||
<div className={classes["icon-container"]}>
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", folder?.uid ?? "")}
|
||||
title="Modifier les collaborateurs">
|
||||
<IconButton icon={<UserGroupIcon title="Modifier les collaborateurs" />} variant={EIconButtonVariant.NEUTRAL} />
|
||||
</Link>
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? "")}
|
||||
title="Modifier les informations du dossiers">
|
||||
<IconButton
|
||||
icon={<PencilSquareIcon title="Modifier les informations du dossiers" />}
|
||||
variant={EIconButtonVariant.NEUTRAL}
|
||||
/>
|
||||
</Link>
|
||||
<IconButton icon={<ArchiveBoxIcon title="Archiver le dossier" />} variant={EIconButtonVariant.ERROR} />
|
||||
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||
<>
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", folder?.uid ?? "")}
|
||||
title="Modifier les collaborateurs">
|
||||
<IconButton
|
||||
icon={<UserGroupIcon title="Modifier les collaborateurs" />}
|
||||
variant={EIconButtonVariant.NEUTRAL}
|
||||
/>
|
||||
</Link>
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? "")}
|
||||
title="Modifier les informations du dossiers">
|
||||
<IconButton
|
||||
icon={<PencilSquareIcon title="Modifier les informations du dossiers" />}
|
||||
variant={EIconButtonVariant.NEUTRAL}
|
||||
/>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
onClick={onArchive}
|
||||
icon={<ArchiveBoxIcon title="Archiver le dossier" />}
|
||||
variant={EIconButtonVariant.ERROR}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -14,7 +14,7 @@ type IProps = {
|
||||
|
||||
export default function DeleteFolderModal(props: IProps) {
|
||||
const { isOpen, onClose, folder } = props;
|
||||
const navigate = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
if (!folder.uid) return;
|
||||
@ -23,17 +23,17 @@ export default function DeleteFolderModal(props: IProps) {
|
||||
|
||||
return Folders.getInstance()
|
||||
.delete(folder.uid)
|
||||
.then(() => navigate.push(Module.getInstance().get().modules.pages.Folder.props.path))
|
||||
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
|
||||
.then(onClose);
|
||||
}, [folder, navigate, onClose]);
|
||||
}, [folder, router, onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={"Êtes-vous sûr de vouloir supprimer ce dossier ?"}
|
||||
firstButton={{ text: "Annuler", onClick: onClose }}
|
||||
secondButton={{ text: "Supprimer le dossier", onClick: onDelete }}>
|
||||
firstButton={{ children: "Annuler", onClick: onClose }}
|
||||
secondButton={{ children: "Supprimer le dossier", onClick: onDelete }}>
|
||||
<Typography typo={ETypo.TEXT_MD_light}>
|
||||
Cette action est irréversible. En supprimant ce dossier, toutes les informations associées seront définitivement perdues.
|
||||
</Typography>
|
||||
|
@ -0,0 +1,11 @@
|
||||
.anchoring {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
.validate-gif {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onAnchor: () => void;
|
||||
};
|
||||
|
||||
export default function RequireAnchoringModal(props: IProps) {
|
||||
const { isOpen, onClose, onAnchor: onAnchorProps } = props;
|
||||
|
||||
const onAnchor = useCallback(() => {
|
||||
onAnchorProps();
|
||||
onClose();
|
||||
}, [onAnchorProps, onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={"Archiver le dossier, action requise : Ancrer et certifier le dossier"}
|
||||
firstButton={{ children: "Annuler", onClick: onClose }}
|
||||
secondButton={{ children: "Ancrer le dossier", onClick: onAnchor }}>
|
||||
<Typography typo={ETypo.TEXT_MD_light}>
|
||||
Pour archiver ce dossier, il est nécessaire de l'ancrer dans la blockchain afin de garantir la sécurité et l'authenticité
|
||||
des documents. Veuillez procéder à l'ancrage avant de continuer.
|
||||
</Typography>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -3,13 +3,21 @@ import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAncho
|
||||
import Loader from "@Front/Components/DesignSystem/Loader";
|
||||
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import ClientView from "./ClientView";
|
||||
import InformationSection from "./InformationSection";
|
||||
import NoClientView from "./NoClientView";
|
||||
import ClientView from "./ClientView";
|
||||
import AnchoringAlertInfo from "./AnchoringAlertInfo";
|
||||
import AnchoringModal from "./AnchoringModal";
|
||||
import useOpenable from "@Front/Hooks/useOpenable";
|
||||
import AnchoringAlertSuccess from "./AnchoringAlertSuccess";
|
||||
import DownloadAnchoringProofModal from "./DownloadAnchoringProofModal";
|
||||
import RequireAnchoringModal from "./RequireAnchoringModal";
|
||||
import ArchiveModal from "./ArchiveModal";
|
||||
|
||||
export enum AnchorStatus {
|
||||
"VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN",
|
||||
@ -23,10 +31,26 @@ export default function FolderInformation(props: IProps) {
|
||||
const [anchorStatus, setAnchorStatus] = useState<AnchorStatus>(AnchorStatus.NOT_ANCHORED);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [folder, setFolder] = useState<OfficeFolder | null>(null);
|
||||
const anchoringModal = useOpenable();
|
||||
const downloadAnchoringProofModal = useOpenable();
|
||||
const requireAnchoringModal = useOpenable();
|
||||
const archiveModal = useOpenable();
|
||||
|
||||
const params = useParams();
|
||||
const folderUid = params["folderUid"] as string;
|
||||
|
||||
const progress = useMemo(() => {
|
||||
const documents = folder?.documents;
|
||||
if (!documents) return 0;
|
||||
const total = documents.length;
|
||||
const validatedDocuments = documents.filter((document) => document.document_status === EDocumentStatus.VALIDATED).length ?? 0;
|
||||
if (total === 0) return 0;
|
||||
const percentage = (validatedDocuments / total) * 100;
|
||||
return isNaN(percentage) ? 0 : percentage;
|
||||
}, [folder]);
|
||||
|
||||
const doesFolderHaveClient = useMemo(() => folder?.customers?.length !== 0, [folder]);
|
||||
|
||||
const fetchFolder = useCallback(async () => {
|
||||
if (!folderUid) return;
|
||||
const query = {
|
||||
@ -77,23 +101,59 @@ export default function FolderInformation(props: IProps) {
|
||||
.catch(() => setAnchorStatus(AnchorStatus.NOT_ANCHORED));
|
||||
}, [folderUid]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = useCallback(() => {
|
||||
setIsLoading(true);
|
||||
fetchFolder()
|
||||
return fetchFolder()
|
||||
.then(() => fetchAnchorStatus())
|
||||
.catch((e) => console.error(e))
|
||||
.finally(() => setIsLoading(false));
|
||||
}, [fetchAnchorStatus, fetchFolder, folderUid]);
|
||||
}, [fetchAnchorStatus, fetchFolder]);
|
||||
|
||||
const doesFolderHaveClient = useMemo(() => folder?.customers?.length !== 0, [folder]);
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const onAnchorSuccess = useCallback(() => fetchData().then(downloadAnchoringProofModal.open), [fetchData, downloadAnchoringProofModal]);
|
||||
|
||||
const onArchive = useCallback(() => {
|
||||
if (anchorStatus === AnchorStatus.NOT_ANCHORED) return requireAnchoringModal.open();
|
||||
archiveModal.open();
|
||||
}, [anchorStatus, archiveModal, requireAnchoringModal]);
|
||||
|
||||
return (
|
||||
<DefaultNotaryDashboard title={"Dossier"} isArchived={false} mobileBackText="Retour aux dossiers">
|
||||
{!isLoading && (
|
||||
<div className={classes["root"]}>
|
||||
<InformationSection folder={folder} />
|
||||
<InformationSection folder={folder} progress={progress} onArchive={onArchive} anchorStatus={anchorStatus} />
|
||||
{progress === 100 && anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||
<AnchoringAlertInfo onAnchor={anchoringModal.open} />
|
||||
)}
|
||||
{anchorStatus === AnchorStatus.VERIFIED_ON_CHAIN && (
|
||||
<AnchoringAlertSuccess onDownloadAnchoringProof={downloadAnchoringProofModal.open} onArchive={archiveModal.open} />
|
||||
)}
|
||||
{folder && !doesFolderHaveClient && <NoClientView folder={folder} anchorStatus={anchorStatus} />}
|
||||
{folder && doesFolderHaveClient && <ClientView folder={folder} anchorStatus={anchorStatus} />}
|
||||
{folderUid && anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||
<AnchoringModal
|
||||
isOpen={anchoringModal.isOpen}
|
||||
onClose={anchoringModal.close}
|
||||
folderUid={folderUid}
|
||||
onAnchorSuccess={onAnchorSuccess}
|
||||
/>
|
||||
)}
|
||||
{folder && anchorStatus === AnchorStatus.VERIFIED_ON_CHAIN && (
|
||||
<DownloadAnchoringProofModal
|
||||
isOpen={downloadAnchoringProofModal.isOpen}
|
||||
onClose={downloadAnchoringProofModal.close}
|
||||
folder={folder}
|
||||
/>
|
||||
)}
|
||||
<RequireAnchoringModal
|
||||
isOpen={requireAnchoringModal.isOpen}
|
||||
onClose={requireAnchoringModal.close}
|
||||
onAnchor={anchoringModal.open}
|
||||
/>
|
||||
{folderUid && <ArchiveModal isOpen={archiveModal.isOpen} onClose={archiveModal.close} folderUid={folderUid} />}
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
|
@ -1,26 +1,26 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
function useOpenable() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
function useOpenable({ defaultOpen } = { defaultOpen: false }) {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
|
||||
const open = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
const open = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
const close = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
setIsOpen((prev) => !prev);
|
||||
}, []);
|
||||
const toggle = useCallback(() => {
|
||||
setIsOpen((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
toggle,
|
||||
};
|
||||
return {
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
toggle,
|
||||
};
|
||||
}
|
||||
|
||||
export default useOpenable;
|
||||
|
Loading…
x
Reference in New Issue
Block a user