refonte client dashboard + responsive

This commit is contained in:
Max S 2024-09-18 14:37:31 +02:00
parent f6c977b288
commit 486eacf686
9 changed files with 334 additions and 109 deletions

View File

@ -700,7 +700,6 @@
*/
&[fullwidthattr="true"] {
width: 100%;
flex: 1;
}
&[fullwidthattr="false"] {

View File

@ -0,0 +1,29 @@
@import "@Themes/constants.scss";
.root {
display: flex;
align-items: center;
gap: var(--spacing-md, 16px);
width: 355px;
padding: var(--spacing-sm, 8px) var(--spacing-md, 16px);
border-radius: var(--notification-radius, 8px);
.content {
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
}
&:hover {
background: var(--neutral-weak-higlight, #f7f8f8);
}
&.unread {
background: var(--notification-unread-default, #fff3ed);
&:hover {
background: var(--notification-unread-hovered, #ffe4d4);
}
}
}

View File

@ -0,0 +1,40 @@
import classNames from "classnames";
import React from "react";
import Typography, { ETypo, ETypoColor } from "../Typography";
import classes from "./classes.module.scss";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant, IButtonProps } from "../Button";
type IProps = {
text: string;
read: boolean;
button: IButtonProps;
className?: string;
};
export default function NotificationBox(props: IProps) {
const { className, text, button, read } = props;
return (
<div className={classNames(classes["root"], className, !read && classes["unread"])}>
<div className={classes["content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_PRIMARY}>
{text}
</Typography>
<Button
{...button}
size={button.size ?? EButtonSize.SM}
variant={button.variant ?? EButtonVariant.SECONDARY}
styletype={button.styletype ?? EButtonstyletype.TEXT}>
{button.children}
</Button>
</div>
{!read && (
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<circle cx="6" cy="6" r="6" fill="#FF4617" />
</svg>
)}
</div>
);
}

View File

@ -1,11 +1,10 @@
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { Contact as ContactCustomer, Note } from "le-coffre-resources/dist/Customer";
import { Contact as ContactNotary } from "le-coffre-resources/dist/Notary";
import classes from "./classes.module.scss";
type IProps = {
contact: ContactCustomer | ContactNotary;
contact: ContactCustomer;
note: Note | null;
};

View File

@ -0,0 +1,37 @@
@import "@Themes/constants.scss";
.root {
width: 100%;
display: flex;
justify-content: space-between;
padding: var(--spacing-md, 16px);
gap: var(--spacing-md, 16px);
background: var(--primary-weak-higlight, #e5eefa);
.left {
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
.note-and-rib-button {
display: none;
@media screen and (max-width: $screen-s) {
display: block;
}
}
}
.right {
margin-right: var(--spacing-xl, 32px);
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
@media screen and (max-width: $screen-s) {
display: none;
}
}
}

View File

@ -0,0 +1,116 @@
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import Customer from "le-coffre-resources/dist/Customer";
import { OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
import { useCallback, useEffect, useMemo, useState } from "react";
import classes from "./classes.module.scss";
import OfficeRib from "@Front/Api/LeCoffreApi/Customer/OfficeRib/OfficeRib";
type IProps = {
folder: OfficeFolderNotary;
customer: Customer;
};
export default function ContactBox(props: IProps) {
const { folder, customer } = props;
const [ribUrl, setRibUrl] = useState<string | null>(null);
const notaryContact = useMemo(
() =>
folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact ??
folder?.stakeholders![0]!.contact,
[folder],
);
const note = useMemo(
() =>
folder?.notes?.find((note) => note.customer?.uid === customer?.uid) ?? {
content: "Aucune note",
created_at: new Date(),
updated_at: new Date(),
},
[customer?.uid, folder?.notes],
);
useEffect(() => {
if (!folder?.office?.uid) return;
OfficeRib.getInstance()
.getRibStream(folder.office.uid)
.then((blob) => setRibUrl(URL.createObjectURL(blob)));
}, [folder]);
const downloadRib = useCallback(async () => {
if (!ribUrl) return;
const a = document.createElement("a");
a.style.display = "none";
a.href = ribUrl;
a.download = "";
document.body.appendChild(a);
a.click();
}, [ribUrl]);
return (
<div className={classes["root"]}>
<div className={classes["left"]}>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Votre notaire
</Typography>
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_PRIMARY_500}>
{notaryContact?.first_name} {notaryContact?.last_name}
</Typography>
</div>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Numéro de téléphone
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
{notaryContact?.cell_phone_number ?? notaryContact?.phone_number ?? "_"}
</Typography>
</div>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
E-mail
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
{notaryContact?.email ?? "_"}
</Typography>
</div>
<div className={classes["note-and-rib-button"]}>
{noteAndRibButton()}
</div>
</div>
<div className={classes["right"]}>{noteAndRibButton()}</div>
</div>
);
function noteAndRibButton() {
return (
<>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Note dossier
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
{note?.content ?? "-"}
</Typography>
</div>
{ribUrl && (
<Button
fullwidth
onClick={downloadRib}
variant={EButtonVariant.PRIMARY}
size={EButtonSize.LG}
styletype={EButtonstyletype.CONTAINED}
rightIcon={<ArrowDownTrayIcon />}>
Télécharger le RIB
</Button>
)}
</>
);
}
}

View File

@ -12,4 +12,10 @@
flex-direction: column;
gap: var(--spacing-sm, 8px);
}
@media screen and (max-width: $screen-s) {
flex-direction: column;
gap: var(--spacing-md, 16px);
align-items: flex-start;
}
}

View File

@ -5,36 +5,54 @@
flex-direction: column;
gap: var(--spacing-xl, 32px);
.title-container {
flex-direction: column;
.top {
display: flex;
gap: var(--spacing-sm, 8px);
gap: var(--spacing-lg, 24px);
.office-container {
.folder-info-container {
flex-direction: column;
display: flex;
align-items: center;
gap: var(--spacing-md, 16px);
width: 100%;
gap: var(--spacing-sm, 8px);
.office-container {
display: flex;
align-items: center;
gap: var(--spacing-md, 16px);
}
}
@media screen and (max-width: $screen-s) {
.desktop-separator {
display: none;
}
flex-direction: column;
}
}
.content {
.documents {
display: flex;
gap: var(--spacing-lg, 24px);
align-items: flex-start;
flex-direction: column;
gap: var(--spacing-xl, 32px);
.notary {
display: flex;
width: 300px;
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-lg, 24px);
}
.documents {
.content {
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
width: 100%;
@media screen and (max-width: $screen-s) {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-md, 16px);
}
@media screen and (max-width: 734px) {
display: grid;
grid-template-columns: 1fr;
gap: var(--spacing-xl, 32px);
}
}
}
}

View File

@ -4,23 +4,24 @@ import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Customer, { Document } from "le-coffre-resources/dist/Customer";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { type OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
import React, { useCallback, useEffect, useState } from "react";
import { DocumentNotary, type OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
import classes from "./classes.module.scss";
import { useRouter } from "next/router";
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
import OfficeRib from "@Front/Api/LeCoffreApi/Customer/OfficeRib/OfficeRib";
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
import DefaultCustomerDashboard from "@Front/Components/LayoutTemplates/DefaultCustomerDashboard";
import ContactBox from "@Front/Components/Elements/ContactBox";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import DepositDocumentComponent from "./DepositDocumentComponent";
import Link from "next/link";
import Module from "@Front/Config/Module";
import Separator, { ESeperatorColor, ESeperatorDirection } from "@Front/Components/DesignSystem/Separator";
import NotificationBox from "@Front/Components/DesignSystem/NotificationBox";
import ContactBox from "./ContactBox";
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
type IProps = {};
@ -31,8 +32,7 @@ export default function ClientDashboard(props: IProps) {
const [customer, setCustomer] = useState<Customer | null>(null);
const [folder, setFolder] = useState<OfficeFolderNotary | null>(null);
const [ribUrl, setRibUrl] = useState<string | null>(null);
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
const fetchFolderAndCustomer = useCallback(async () => {
let jwt: ICustomerJwtPayload | undefined;
@ -55,6 +55,11 @@ export default function ClientDashboard(props: IProps) {
office_role: true,
},
},
deed: {
include: {
deed_type: true,
},
},
},
});
@ -99,95 +104,71 @@ export default function ClientDashboard(props: IProps) {
fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid));
}, [fetchDocuments, fetchFolderAndCustomer]);
const notaryContact = useMemo(
() =>
folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact ??
folder?.stakeholders![0]!.contact,
[folder],
);
const note = useMemo(
() =>
folder?.notes?.find((note) => note.customer?.uid === customer?.uid) ?? {
content: "Aucune note",
created_at: new Date(),
updated_at: new Date(),
},
[customer?.uid, folder?.notes],
);
useEffect(() => {
if (!folder?.office?.uid) return;
OfficeRib.getInstance()
.getRibStream(folder.office.uid)
.then((blob) => setRibUrl(URL.createObjectURL(blob)));
}, [folder]);
const downloadRib = useCallback(async () => {
if (!ribUrl) return;
const a = document.createElement("a");
a.style.display = "none";
a.href = ribUrl;
a.download = "";
document.body.appendChild(a);
a.click();
}, [ribUrl]);
const customerUid = JwtService.getInstance().decodeCustomerJwt()?.customerId;
if (!folderUid || !customerUid) return;
DocumentsNotary.getInstance()
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
.then((documentsNotary) => setDocumentsNotary(documentsNotary));
}, [folderUid]);
return (
<DefaultCustomerDashboard>
<div className={classes["root"]}>
<div className={classes["title-container"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Dossier {folder?.folder_number} - {folder?.name}
</Typography>
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
</Typography>
<Tag color={ETagColor.INFO} label={"todo"} />
<div className={classes["office-container"]}>
<div className={classes["top"]}>
<div className={classes["folder-info-container"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Office
Dossier {folder?.folder_number} - {folder?.name}
</Typography>
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
</Typography>
<Tag color={ETagColor.INFO} label={folder?.deed?.deed_type?.name ?? ""} />
<div className={classes["office-container"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Office
</Typography>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_ACCENT}>
{folder?.office?.name}
</Typography>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_ACCENT}>
{folder?.office?.name}
</Typography>
</div>
</div>
<Separator
className={classes["desktop-separator"]}
direction={ESeperatorDirection.VERTICAL}
size={142}
color={ESeperatorColor.LIGHT}
/>
{(documentsNotary?.length ?? 0) > 0 && (
<NotificationBox
text={`${documentsNotary?.length} Nouveaux document(s) envoyé(s) par votre notaire`}
button={{
children: "Consulter les documents",
variant: EButtonVariant.SECONDARY,
styletype: EButtonstyletype.OUTLINED,
onClick: () =>
router.push(
Module.getInstance()
.get()
.modules.pages.ClientDashboard.pages.ReceiveDocuments.props.path.replace(
"[folderUid]",
folderUid as string,
),
),
}}
read={false}
/>
)}
</div>
<div className={classes["content"]}>
<div className={classes["notary"]}>
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.TABLE_COLUMN_CONTRAST}>
Votre Notaire
</Typography>
{notaryContact && <ContactBox contact={notaryContact} note={note} />}
{ribUrl && (
<Button
fullwidth
onClick={downloadRib}
variant={EButtonVariant.PRIMARY}
size={EButtonSize.LG}
styletype={EButtonstyletype.CONTAINED}
rightIcon={<ArrowDownTrayIcon />}>
Télécharger le RIB
</Button>
)}
<Link
href={Module.getInstance()
.get()
.modules.pages.ClientDashboard.pages.ReceiveDocuments.props.path.replace(
"[folderUid]",
folderUid as string,
)}
style={{ width: "100%" }}>
<Button fullwidth variant={EButtonVariant.PRIMARY} size={EButtonSize.LG} styletype={EButtonstyletype.OUTLINED}>
Voir les documents reçus
</Button>
</Link>
</div>
<div className={classes["documents"]}>
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.TABLE_COLUMN_CONTRAST}>
Documents à envoyer
</Typography>
{customer && folder && <ContactBox customer={customer} folder={folder} />}
<div className={classes["documents"]}>
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.TABLE_COLUMN_CONTRAST}>
Documents à envoyer
</Typography>
<div className={classes["content"]}>
{documents?.map((document) => (
<DepositDocumentComponent
key={document.uid}