✨ refonte client dashboard + responsive
This commit is contained in:
parent
f6c977b288
commit
486eacf686
@ -700,7 +700,6 @@
|
||||
*/
|
||||
&[fullwidthattr="true"] {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&[fullwidthattr="false"] {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
40
src/front/Components/DesignSystem/NotificationBox/index.tsx
Normal file
40
src/front/Components/DesignSystem/NotificationBox/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user