Merge branch 'staging' into preprod
This commit is contained in:
commit
1efb6a85df
849
package-lock.json
generated
849
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -25,10 +25,12 @@
|
|||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"eslint": "8.36.0",
|
"eslint": "8.36.0",
|
||||||
"eslint-config-next": "13.2.4",
|
"eslint-config-next": "13.2.4",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"heroicons": "^2.1.5",
|
"heroicons": "^2.1.5",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.151",
|
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.160",
|
||||||
"next": "^14.2.3",
|
"next": "^14.2.3",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@ -43,6 +45,7 @@
|
|||||||
"uuidv4": "^6.2.13"
|
"uuidv4": "^6.2.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/react-gtm-module": "^2.0.3"
|
"@types/react-gtm-module": "^2.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,4 +87,14 @@ export default class Customers extends BaseNotary {
|
|||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async sendReminder(uid: string, documentsUid: string[]): Promise<void> {
|
||||||
|
const url = new URL(this.baseURl.concat(`/${uid}/send_reminder`));
|
||||||
|
try {
|
||||||
|
await this.postRequest<void>(url, { documentsUid });
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
|
||||||
|
|
||||||
|
import BaseNotary from "../BaseNotary";
|
||||||
|
|
||||||
|
// TODO Type get query params -> Where + inclue + orderby
|
||||||
|
export interface IGetDocumentRemindersparams {
|
||||||
|
where?: {};
|
||||||
|
include?: {};
|
||||||
|
orderBy?: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Type getbyuid query params
|
||||||
|
|
||||||
|
export default class DocumentReminders extends BaseNotary {
|
||||||
|
private static instance: DocumentReminders;
|
||||||
|
private readonly baseURl = this.namespaceUrl.concat("/document_reminders");
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance() {
|
||||||
|
if (!this.instance) {
|
||||||
|
return new this();
|
||||||
|
} else {
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(q: IGetDocumentRemindersparams): Promise<DocumentReminder[]> {
|
||||||
|
const url = new URL(this.baseURl);
|
||||||
|
const query = { q };
|
||||||
|
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||||
|
try {
|
||||||
|
return await this.getRequest<DocumentReminder[]>(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
|
||||||
|
|
||||||
|
import BaseNotary from "../BaseNotary";
|
||||||
|
|
||||||
|
export interface IGetDocumentNotaryparams {
|
||||||
|
where?: {};
|
||||||
|
include?: {};
|
||||||
|
orderBy?: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DocumentsNotary extends BaseNotary {
|
||||||
|
private static instance: DocumentsNotary;
|
||||||
|
private readonly baseURl = this.namespaceUrl.concat("/documents_notary");
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance() {
|
||||||
|
if (!this.instance) {
|
||||||
|
return new this();
|
||||||
|
} else {
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(q: IGetDocumentNotaryparams): Promise<DocumentNotary[]> {
|
||||||
|
const url = new URL(this.baseURl);
|
||||||
|
const query = { q };
|
||||||
|
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||||
|
try {
|
||||||
|
return await this.getRequest<DocumentNotary[]>(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Create a Document Notary
|
||||||
|
*/
|
||||||
|
public async post(body: any): Promise<DocumentNotary> {
|
||||||
|
const url = new URL(this.baseURl);
|
||||||
|
try {
|
||||||
|
return await this.postRequestFormData<DocumentNotary>(url, body);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Delete a Document Notary
|
||||||
|
*/
|
||||||
|
public async delete(uid: string): Promise<void> {
|
||||||
|
const url = new URL(this.baseURl);
|
||||||
|
url.pathname = url.pathname.concat(`/${uid}`);
|
||||||
|
try {
|
||||||
|
await this.deleteRequest(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
src/front/Api/LeCoffreApi/Notary/FilesNotary/Files.ts
Normal file
71
src/front/Api/LeCoffreApi/Notary/FilesNotary/Files.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { File } from "le-coffre-resources/dist/SuperAdmin";
|
||||||
|
|
||||||
|
import BaseNotary from "../BaseNotary";
|
||||||
|
|
||||||
|
export interface IGetFilesparams {
|
||||||
|
where?: {};
|
||||||
|
include?: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IPutFilesParams = {};
|
||||||
|
|
||||||
|
export interface IPostFilesParams {}
|
||||||
|
|
||||||
|
export default class FilesNotary extends BaseNotary {
|
||||||
|
private static instance: FilesNotary;
|
||||||
|
private readonly baseURl = this.namespaceUrl.concat("/files-notary");
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance() {
|
||||||
|
return (this.instance ??= new this());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(q: IGetFilesparams): Promise<File[]> {
|
||||||
|
const url = new URL(this.baseURl);
|
||||||
|
const query = { q };
|
||||||
|
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||||
|
try {
|
||||||
|
const files = await this.getRequest<File[]>(url);
|
||||||
|
return files;
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getByUid(uid: string, q?: any): Promise<File> {
|
||||||
|
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||||
|
const query = { q };
|
||||||
|
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||||
|
try {
|
||||||
|
const file = await this.getRequest<File>(url);
|
||||||
|
return file;
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(uid: string): Promise<File> {
|
||||||
|
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||||
|
try {
|
||||||
|
return await this.deleteRequest<File>(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async download(uid: string): Promise<any> {
|
||||||
|
const url = new URL(this.baseURl.concat(`/download/${uid}`));
|
||||||
|
try {
|
||||||
|
return await this.getRequest<any>(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ export interface IGetFoldersParams {
|
|||||||
select?: {};
|
select?: {};
|
||||||
where?: {};
|
where?: {};
|
||||||
include?: {};
|
include?: {};
|
||||||
|
orderBy?: {};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,36 @@
|
|||||||
border: 1px solid var(--alerts-info-border);
|
border: 1px solid var(--alerts-info-border);
|
||||||
background: var(--alerts-info-background);
|
background: var(--alerts-info-background);
|
||||||
|
|
||||||
|
@media screen and (max-width: 680px) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
align-self: stretch;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
display: block;
|
||||||
|
&.desktop {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
@media screen and (max-width: 680px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobile {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media screen and (max-width: 680px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -25,64 +55,34 @@
|
|||||||
.button-container {
|
.button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-md, 16px);
|
gap: var(--spacing-md, 16px);
|
||||||
|
|
||||||
|
@media screen and (max-width: 680px) {
|
||||||
|
flex-direction: column;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
padding: var(--spacing-1, 8px);
|
|
||||||
align-items: center;
|
|
||||||
align-self: flex-start;
|
|
||||||
|
|
||||||
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 {
|
&.error {
|
||||||
border-color: var(--alerts-error-border);
|
border-color: var(--alerts-error-border);
|
||||||
background: var(--alerts-error-background);
|
background: var(--alerts-error-background);
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
stroke: var(--alerts-badge-contrast-error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.warning {
|
&.warning {
|
||||||
border-color: var(--alerts-warning-border);
|
border-color: var(--alerts-warning-border);
|
||||||
background: var(--alerts-warning-background);
|
background: var(--alerts-warning-background);
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
stroke: var(--alerts-badge-contrast-warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
border-color: var(--alerts-success-border);
|
border-color: var(--alerts-success-border);
|
||||||
background: var(--alerts-success-background);
|
background: var(--alerts-success-background);
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
stroke: var(--alerts-badge-contrast-success);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.neutral {
|
&.neutral {
|
||||||
border-color: var(--alerts-neutral-border);
|
border-color: var(--alerts-neutral-border);
|
||||||
background: var(--alerts-neutral-background);
|
background: var(--alerts-neutral-background);
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
stroke: var(--alerts-badge-contrast-neutral);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fullwidth {
|
&.fullwidth {
|
||||||
|
@ -7,6 +7,7 @@ import Button, { EButtonSize, EButtonstyletype, EButtonVariant, IButtonProps } f
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import IconButton from "../IconButton";
|
import IconButton from "../IconButton";
|
||||||
import useOpenable from "@Front/Hooks/useOpenable";
|
import useOpenable from "@Front/Hooks/useOpenable";
|
||||||
|
import BadgeIcon, { EBadgeColor } from "../BadgeIcon";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
variant: EAlertVariant;
|
variant: EAlertVariant;
|
||||||
@ -35,6 +36,14 @@ const variantButtonMap: Record<EAlertVariant, EButtonVariant> = {
|
|||||||
[EAlertVariant.NEUTRAL]: EButtonVariant.NEUTRAL,
|
[EAlertVariant.NEUTRAL]: EButtonVariant.NEUTRAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const variantColorMap: Record<EAlertVariant, EBadgeColor> = {
|
||||||
|
[EAlertVariant.INFO]: EBadgeColor.INFO,
|
||||||
|
[EAlertVariant.SUCCESS]: EBadgeColor.SUCCESS,
|
||||||
|
[EAlertVariant.WARNING]: EBadgeColor.WARNING,
|
||||||
|
[EAlertVariant.ERROR]: EBadgeColor.ERROR,
|
||||||
|
[EAlertVariant.NEUTRAL]: EBadgeColor.NEUTRAL,
|
||||||
|
};
|
||||||
|
|
||||||
export default function Alert(props: IProps) {
|
export default function Alert(props: IProps) {
|
||||||
const { isOpen, close } = useOpenable({ defaultOpen: true });
|
const { isOpen, close } = useOpenable({ defaultOpen: true });
|
||||||
const { variant = EAlertVariant.INFO, title, description, firstButton, secondButton, closeButton, icon, fullWidth } = props;
|
const { variant = EAlertVariant.INFO, title, description, firstButton, secondButton, closeButton, icon, fullWidth } = props;
|
||||||
@ -43,7 +52,13 @@ export default function Alert(props: IProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(classes["root"], classes[variant], fullWidth && classes["fullwidth"])}>
|
<div className={classNames(classes["root"], classes[variant], fullWidth && classes["fullwidth"])}>
|
||||||
<span className={classes["icon"]}>{icon ?? <InformationCircleIcon />}</span>
|
<div className={classes["top"]}>
|
||||||
|
<BadgeIcon icon={icon ?? <InformationCircleIcon />} color={variantColorMap[variant]} />
|
||||||
|
{closeButton && (
|
||||||
|
<IconButton className={classNames(classes["close-button"], classes["mobile"])} onClick={close} icon={<XMarkIcon />} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={classes["content"]}>
|
<div className={classes["content"]}>
|
||||||
<div className={classes["text-container"]}>
|
<div className={classes["text-container"]}>
|
||||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||||
@ -75,7 +90,7 @@ export default function Alert(props: IProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{closeButton && <IconButton onClick={close} icon={<XMarkIcon />} />}
|
{closeButton && <IconButton className={classNames(classes["close-button"], classes["desktop"])} onClick={close} icon={<XMarkIcon />} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ export default function Autocomplete(props: IProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchValue) {
|
if (searchValue) {
|
||||||
const filteredOptions = options.filter((option) => getLabel(option)?.toLowerCase().includes(searchValue.toLowerCase()));
|
const filteredOptions = options.filter((option) => getLabel(option)?.toLowerCase().includes(searchValue.toLowerCase()));
|
||||||
console.log(filteredOptions);
|
|
||||||
if (filteredOptions.length === 0)
|
if (filteredOptions.length === 0)
|
||||||
return setFilteredOptions([{ id: "no-results", label: "Aucun résulats", notSelectable: true }]);
|
return setFilteredOptions([{ id: "no-results", label: "Aucun résulats", notSelectable: true }]);
|
||||||
return setFilteredOptions(filteredOptions);
|
return setFilteredOptions(filteredOptions);
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
padding: var(--spacing-1, 8px);
|
||||||
|
align-items: center;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
svg {
|
||||||
|
stroke: var(--alerts-badge-contrast-error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
svg {
|
||||||
|
stroke: var(--alerts-badge-contrast-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
svg {
|
||||||
|
stroke: var(--alerts-badge-contrast-success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.neutral {
|
||||||
|
svg {
|
||||||
|
stroke: var(--alerts-badge-contrast-neutral);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/front/Components/DesignSystem/BadgeIcon/index.tsx
Normal file
23
src/front/Components/DesignSystem/BadgeIcon/index.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
export enum EBadgeColor {
|
||||||
|
INFO = "info",
|
||||||
|
SUCCESS = "success",
|
||||||
|
WARNING = "warning",
|
||||||
|
ERROR = "error",
|
||||||
|
NEUTRAL = "neutral",
|
||||||
|
}
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
color: EBadgeColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function BadgeIcon(props: IProps) {
|
||||||
|
const { icon, color } = props;
|
||||||
|
|
||||||
|
return <div className={classNames(classes["root"], classes[color])}>{icon}</div>;
|
||||||
|
}
|
@ -26,8 +26,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
&[variant="primary"] {
|
&[variant="primary"] {
|
||||||
color: var(--button-contained-primary-hovered-contrast);
|
color: var(--button-contained-primary-default-contrast);
|
||||||
border: 1px solid var(--button-contained-primary-default-border);
|
border-color: var(--button-contained-primary-default-border);
|
||||||
background: var(--button-contained-primary-default-background);
|
background: var(--button-contained-primary-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid var(--button-contained-primary-hovered-border);
|
border-color: var(--button-contained-primary-hovered-border);
|
||||||
background: var(--button-contained-primary-hovered-background);
|
background: var(--button-contained-primary-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -46,7 +46,7 @@
|
|||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--button-contained-primary-default-contrast);
|
color: var(--button-contained-primary-default-contrast);
|
||||||
border: 1px solid var(--button-contained-primary-pressed-border);
|
border-color: var(--button-contained-primary-pressed-border);
|
||||||
background: var(--button-contained-primary-pressed-background);
|
background: var(--button-contained-primary-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
&[styletype="outlined"] {
|
&[styletype="outlined"] {
|
||||||
color: var(--button-outlined-primary-default-contrast);
|
color: var(--button-outlined-primary-default-contrast);
|
||||||
border: 1px solid var(--button-outlined-primary-default-border);
|
border-color: var(--button-outlined-primary-default-border);
|
||||||
background: var(--button-outlined-primary-default-background);
|
background: var(--button-outlined-primary-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -64,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid var(--button-outlined-primary-hovered-border);
|
border-color: var(--button-outlined-primary-hovered-border);
|
||||||
background: var(--button-outlined-primary-hovered-background);
|
background: var(--button-outlined-primary-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -75,7 +75,7 @@
|
|||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--button-outlined-primary-pressed-contrast);
|
color: var(--button-outlined-primary-pressed-contrast);
|
||||||
border: 1px solid var(--button-outlined-primary-pressed-border);
|
border-color: var(--button-outlined-primary-pressed-border);
|
||||||
background: var(--button-outlined-primary-pressed-background);
|
background: var(--button-outlined-primary-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -86,16 +86,16 @@
|
|||||||
|
|
||||||
&[styletype="text"] {
|
&[styletype="text"] {
|
||||||
color: var(--button-text-primary-default-contrast);
|
color: var(--button-text-primary-default-contrast);
|
||||||
border-bottom: 1px solid var(--button-text-primary-default-border);
|
border-color: var(--button-text-primary-default-border);
|
||||||
background: var(--button-text-primary-default-background);
|
background: var(--button-text-primary-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-primary-default-contrast);
|
stroke: var(--button-text-primary-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-bottom: 1px solid var(--button-outlined-primary-hovered-border);
|
color: var(--button-text-primary-hovered-contrast);
|
||||||
background: var(--button-outlined-primary-default-background);
|
border-color: var(--button-text-primary-hovered-border);
|
||||||
|
background: var(--button-text-primary-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-primary-hovered-contrast);
|
stroke: var(--button-text-primary-hovered-contrast);
|
||||||
@ -104,9 +104,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--color-primary-800);
|
color: var(--button-text-primary-pressed-contrast);
|
||||||
background: var(--button-outlined-primary-default-background);
|
border-color: var(--button-text-primary-pressed-border);
|
||||||
border-bottom: 1px solid var(--color-primary-800);
|
background: var(--button-text-primary-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-primary-pressed-contrast);
|
stroke: var(--button-text-primary-pressed-contrast);
|
||||||
@ -117,17 +117,16 @@
|
|||||||
|
|
||||||
&[variant="secondary"] {
|
&[variant="secondary"] {
|
||||||
color: var(--button-contained-secondary-default-contrast);
|
color: var(--button-contained-secondary-default-contrast);
|
||||||
background: var(--button-contained-secondary-default-background);
|
|
||||||
border-color: var(--button-contained-secondary-default-border);
|
border-color: var(--button-contained-secondary-default-border);
|
||||||
|
background: var(--button-contained-secondary-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-secondary-default-contrast);
|
stroke: var(--button-contained-secondary-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--button-contained-secondary-hovered-contrast);
|
|
||||||
background: var(--button-contained-secondary-hovered-background);
|
|
||||||
border-color: var(--button-contained-secondary-hovered-border);
|
border-color: var(--button-contained-secondary-hovered-border);
|
||||||
|
background: var(--button-contained-secondary-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-secondary-hovered-contrast);
|
stroke: var(--button-contained-secondary-hovered-contrast);
|
||||||
@ -136,9 +135,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--button-contained-secondary-pressed-contrast);
|
color: var(--button-contained-secondary-default-contrast);
|
||||||
background: var(--button-contained-secondary-pressed-background);
|
|
||||||
border-color: var(--button-contained-secondary-pressed-border);
|
border-color: var(--button-contained-secondary-pressed-border);
|
||||||
|
background: var(--button-contained-secondary-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-secondary-pressed-contrast);
|
stroke: var(--button-contained-secondary-pressed-contrast);
|
||||||
@ -147,14 +146,15 @@
|
|||||||
|
|
||||||
&[styletype="outlined"] {
|
&[styletype="outlined"] {
|
||||||
color: var(--button-outlined-secondary-default-contrast);
|
color: var(--button-outlined-secondary-default-contrast);
|
||||||
border: 1px solid var(--button-outlined-secondary-default-border);
|
border-color: var(--button-outlined-secondary-default-border);
|
||||||
background: var(--button-outlined-secondary-default-background);
|
background: var(--button-outlined-secondary-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-secondary-default-contrast);
|
stroke: var(--button-outlined-secondary-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid var(--button-outlined-secondary-hovered-border);
|
border-color: var(--button-outlined-secondary-hovered-border);
|
||||||
background: var(--button-outlined-secondary-hovered-background);
|
background: var(--button-outlined-secondary-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -165,7 +165,7 @@
|
|||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--button-outlined-secondary-pressed-contrast);
|
color: var(--button-outlined-secondary-pressed-contrast);
|
||||||
border: 1px solid var(--button-outlined-secondary-pressed-border);
|
border-color: var(--button-outlined-secondary-pressed-border);
|
||||||
background: var(--button-outlined-secondary-pressed-background);
|
background: var(--button-outlined-secondary-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -176,15 +176,16 @@
|
|||||||
|
|
||||||
&[styletype="text"] {
|
&[styletype="text"] {
|
||||||
color: var(--button-text-secondary-default-contrast);
|
color: var(--button-text-secondary-default-contrast);
|
||||||
border-bottom: 1px solid var(--button-text-secondary-default-border);
|
border-color: var(--button-text-secondary-default-border);
|
||||||
background: var(--button-text-secondary-default-background);
|
background: var(--button-text-secondary-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-secondary-default-contrast);
|
stroke: var(--button-text-secondary-default-contrast);
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
border-bottom: 1px solid var(--button-outlined-secondary-hovered-border);
|
color: var(--button-text-secondary-hovered-contrast);
|
||||||
background: var(--button-outlined-secondary-default-background);
|
border-color: var(--button-text-secondary-hovered-border);
|
||||||
|
background: var(--button-text-secondary-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-secondary-hovered-contrast);
|
stroke: var(--button-text-secondary-hovered-contrast);
|
||||||
@ -193,9 +194,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--button-outlined-secondary-pressed-contrast);
|
color: var(--button-text-secondary-pressed-contrast);
|
||||||
border-bottom: 1px solid var(--button-outlined-secondary-pressed-border);
|
border-color: var(--button-text-secondary-pressed-border);
|
||||||
background: var(--button-outlined-secondary-pressed-background);
|
background: var(--button-text-secondary-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-secondary-pressed-contrast);
|
stroke: var(--button-text-secondary-pressed-contrast);
|
||||||
@ -205,8 +206,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[variant="neutral"] {
|
&[variant="neutral"] {
|
||||||
color: var(--button-contained-neutral-hovered-contrast);
|
color: var(--button-contained-neutral-default-contrast);
|
||||||
border: 1px solid var(--button-contained-neutral-default-border);
|
border-color: var(--button-contained-neutral-default-border);
|
||||||
background: var(--button-contained-neutral-default-background);
|
background: var(--button-contained-neutral-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -214,7 +215,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid var(--button-contained-neutral-hovered-border);
|
border-color: var(--button-contained-neutral-hovered-border);
|
||||||
background: var(--button-contained-neutral-hovered-background);
|
background: var(--button-contained-neutral-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -225,7 +226,7 @@
|
|||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--button-contained-neutral-default-contrast);
|
color: var(--button-contained-neutral-default-contrast);
|
||||||
border: 1px solid var(--button-contained-neutral-pressed-border);
|
border-color: var(--button-contained-neutral-pressed-border);
|
||||||
background: var(--button-contained-neutral-pressed-background);
|
background: var(--button-contained-neutral-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -235,15 +236,17 @@
|
|||||||
|
|
||||||
&[styletype="outlined"] {
|
&[styletype="outlined"] {
|
||||||
color: var(--button-outlined-neutral-default-contrast);
|
color: var(--button-outlined-neutral-default-contrast);
|
||||||
border: 1px solid var(--button-outlined-neutral-default-border);
|
border-color: var(--button-outlined-neutral-default-border);
|
||||||
background: var(--button-outlined-neutral-default-background);
|
background: var(--button-outlined-neutral-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-neutral-default-contrast);
|
stroke: var(--button-outlined-neutral-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid var(--button-outlined-neutral-hovered-border);
|
border-color: var(--button-outlined-neutral-hovered-border);
|
||||||
background: var(--button-outlined-neutral-hovered-background);
|
background: var(--button-outlined-neutral-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-neutral-hovered-contrast);
|
stroke: var(--button-outlined-neutral-hovered-contrast);
|
||||||
}
|
}
|
||||||
@ -252,7 +255,7 @@
|
|||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--button-outlined-neutral-pressed-contrast);
|
color: var(--button-outlined-neutral-pressed-contrast);
|
||||||
border: 1px solid var(--button-outlined-neutral-pressed-border);
|
border-color: var(--button-outlined-neutral-pressed-border);
|
||||||
background: var(--button-outlined-neutral-pressed-background);
|
background: var(--button-outlined-neutral-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -263,15 +266,16 @@
|
|||||||
|
|
||||||
&[styletype="text"] {
|
&[styletype="text"] {
|
||||||
color: var(--button-text-neutral-default-contrast);
|
color: var(--button-text-neutral-default-contrast);
|
||||||
border-bottom: 1px solid var(--button-text-neutral-default-border);
|
border-color: var(--button-text-neutral-default-border);
|
||||||
background: var(--button-text-neutral-default-background);
|
background: var(--button-text-neutral-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-neutral-default-contrast);
|
stroke: var(--button-text-neutral-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-bottom: 1px solid var(--button-outlined-neutral-hovered-border);
|
color: var(--button-text-neutral-hovered-contrast);
|
||||||
background: var(--button-outlined-neutral-default-background);
|
border-color: var(--button-text-neutral-hovered-border);
|
||||||
|
background: var(--button-text-neutral-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-neutral-hovered-contrast);
|
stroke: var(--button-text-neutral-hovered-contrast);
|
||||||
@ -280,9 +284,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--color-primary-800);
|
color: var(--button-text-neutral-pressed-contrast);
|
||||||
background: var(--button-outlined-neutral-default-background);
|
border-color: var(--button-text-neutral-pressed-border);
|
||||||
border-bottom: 1px solid var(--color-primary-800);
|
background: var(--button-text-neutral-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-neutral-pressed-contrast);
|
stroke: var(--button-text-neutral-pressed-contrast);
|
||||||
@ -292,17 +296,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[variant="error"] {
|
&[variant="error"] {
|
||||||
color: var(--color-error-600);
|
color: var(--button-contained-error-default-contrast);
|
||||||
background: var(--color-error-600);
|
border-color: var(--button-contained-error-default-border);
|
||||||
border-color: var(--color-error-600);
|
background: var(--button-contained-error-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-error-default-contrast);
|
stroke: var(--button-contained-error-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-error-800);
|
border-color: var(--button-contained-error-hovered-border);
|
||||||
border-color: var(--color-error-800);
|
background: var(--button-contained-error-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-error-hovered-contrast);
|
stroke: var(--button-contained-error-hovered-contrast);
|
||||||
@ -311,8 +315,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background: var(--color-error-900);
|
color: var(--button-contained-error-default-contrast);
|
||||||
border-color: var(--color-error-900);
|
border-color: var(--button-contained-error-pressed-border);
|
||||||
|
background: var(--button-contained-error-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-error-pressed-contrast);
|
stroke: var(--button-contained-error-pressed-contrast);
|
||||||
@ -320,13 +325,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[styletype="outlined"] {
|
&[styletype="outlined"] {
|
||||||
|
color: var(--button-outlined-error-default-contrast);
|
||||||
|
border-color: var(--button-outlined-error-default-border);
|
||||||
|
background: var(--button-outlined-error-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-error-default-contrast);
|
stroke: var(--button-outlined-error-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-error-50);
|
border-color: var(--button-outlined-error-hovered-border);
|
||||||
border-color: var(--color-secondary-700);
|
background: var(--button-outlined-error-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-error-hovered-contrast);
|
stroke: var(--button-outlined-error-hovered-contrast);
|
||||||
@ -335,9 +344,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--color-error-100);
|
color: var(--button-outlined-error-pressed-contrast);
|
||||||
color: var(--color-secondary-700);
|
border-color: var(--button-outlined-error-pressed-border);
|
||||||
border-color: var(--color-secondary-700);
|
background: var(--button-outlined-error-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-error-pressed-contrast);
|
stroke: var(--button-outlined-error-pressed-contrast);
|
||||||
@ -346,14 +355,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[styletype="text"] {
|
&[styletype="text"] {
|
||||||
|
color: var(--button-text-error-default-contrast);
|
||||||
|
border-color: var(--button-text-error-default-border);
|
||||||
|
background: var(--button-text-error-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-error-default-contrast);
|
stroke: var(--button-text-error-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent;
|
color: var(--button-text-error-hovered-contrast);
|
||||||
color: var(--color-error-800);
|
border-color: var(--button-text-error-hovered-border);
|
||||||
border-color: var(--color-error-800);
|
background: var(--button-text-error-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-error-hovered-contrast);
|
stroke: var(--button-text-error-hovered-contrast);
|
||||||
@ -362,9 +374,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: transparent;
|
color: var(--button-text-error-pressed-contrast);
|
||||||
color: var(--color-error-900);
|
border-color: var(--button-text-error-pressed-border);
|
||||||
border-color: var(--color-error-900);
|
background: var(--button-text-error-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-error-pressed-contrast);
|
stroke: var(--button-text-error-pressed-contrast);
|
||||||
@ -373,101 +385,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[variant="warning"] {
|
|
||||||
color: var(--color-warning-600);
|
|
||||||
background: var(--color-warning-600);
|
|
||||||
border-color: var(--color-warning-600);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-contained-warning-default-contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--color-warning-800);
|
|
||||||
border-color: var(--color-warning-800);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-contained-warning-hovered-contrast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
background: var(--color-warning-900);
|
|
||||||
border-color: var(--color-warning-900);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-contained-warning-pressed-contrast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[styletype="outlined"] {
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-outlined-warning-default-contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-warning-50);
|
|
||||||
border-color: var(--color-warning-700);
|
|
||||||
color: var(--color-warning-700);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-outlined-warning-hovered-contrast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
background-color: var(--color-warning-100);
|
|
||||||
color: var(--color-warning-700);
|
|
||||||
border-color: var(--color-warning-700);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-outlined-warning-pressed-contrast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[styletype="text"] {
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-text-warning-default-contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--color-warning-800);
|
|
||||||
border-color: var(--color-warning-800);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-text-warning-hovered-contrast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--color-warning-900);
|
|
||||||
border-color: var(--color-warning-900);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke: var(--button-text-warning-pressed-contrast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[variant="success"] {
|
&[variant="success"] {
|
||||||
color: var(--color-success-600);
|
color: var(--button-contained-success-default-contrast);
|
||||||
background: var(--color-success-600);
|
border-color: var(--button-contained-success-default-border);
|
||||||
border-color: var(--color-success-600);
|
background: var(--button-contained-success-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-success-default-contrast);
|
stroke: var(--button-contained-success-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-success-800);
|
border-color: var(--button-contained-success-hovered-border);
|
||||||
border-color: var(--color-success-800);
|
background: var(--button-contained-success-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-success-hovered-contrast);
|
stroke: var(--button-contained-success-hovered-contrast);
|
||||||
@ -476,8 +405,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background: var(--color-success-900);
|
color: var(--button-contained-success-default-contrast);
|
||||||
border-color: var(--color-success-900);
|
border-color: var(--button-contained-success-pressed-border);
|
||||||
|
background: var(--button-contained-success-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-success-pressed-contrast);
|
stroke: var(--button-contained-success-pressed-contrast);
|
||||||
@ -485,14 +415,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[styletype="outlined"] {
|
&[styletype="outlined"] {
|
||||||
|
color: var(--button-outlined-success-default-contrast);
|
||||||
|
border-color: var(--button-outlined-success-default-border);
|
||||||
|
background: var(--button-outlined-success-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-success-default-contrast);
|
stroke: var(--button-outlined-success-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-success-50);
|
border-color: var(--button-outlined-success-hovered-border);
|
||||||
border-color: var(--color-success-700);
|
background: var(--button-outlined-success-hovered-background);
|
||||||
color: var(--color-success-700);
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-success-hovered-contrast);
|
stroke: var(--button-outlined-success-hovered-contrast);
|
||||||
@ -501,9 +434,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--color-success-100);
|
color: var(--button-outlined-success-pressed-contrast);
|
||||||
color: var(--color-success-700);
|
border-color: var(--button-outlined-success-pressed-border);
|
||||||
border-color: var(--color-success-700);
|
background: var(--button-outlined-success-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-success-pressed-contrast);
|
stroke: var(--button-outlined-success-pressed-contrast);
|
||||||
@ -512,14 +445,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[styletype="text"] {
|
&[styletype="text"] {
|
||||||
|
color: var(--button-text-success-default-contrast);
|
||||||
|
border-color: var(--button-text-success-default-border);
|
||||||
|
background: var(--button-text-success-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-success-default-contrast);
|
stroke: var(--button-text-success-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent;
|
color: var(--button-text-success-hovered-contrast);
|
||||||
color: var(--color-success-800);
|
border-color: var(--button-text-success-hovered-border);
|
||||||
border-color: var(--color-success-800);
|
background: var(--button-text-success-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-success-hovered-contrast);
|
stroke: var(--button-text-success-hovered-contrast);
|
||||||
@ -528,9 +464,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: transparent;
|
color: var(--button-text-success-pressed-contrast);
|
||||||
color: var(--color-success-900);
|
border-color: var(--button-text-success-pressed-border);
|
||||||
border-color: var(--color-success-900);
|
background: var(--button-text-success-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-success-pressed-contrast);
|
stroke: var(--button-text-success-pressed-contrast);
|
||||||
@ -539,18 +475,108 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[variant="warning"] {
|
||||||
|
color: var(--button-contained-warning-default-contrast);
|
||||||
|
border-color: var(--button-contained-warning-default-border);
|
||||||
|
background: var(--button-contained-warning-default-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-contained-warning-default-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--button-contained-warning-hovered-border);
|
||||||
|
background: var(--button-contained-warning-hovered-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-contained-warning-hovered-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
color: var(--button-contained-warning-default-contrast);
|
||||||
|
border-color: var(--button-contained-warning-pressed-border);
|
||||||
|
background: var(--button-contained-warning-pressed-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-contained-warning-pressed-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[styletype="outlined"] {
|
||||||
|
color: var(--button-outlined-warning-default-contrast);
|
||||||
|
border-color: var(--button-outlined-warning-default-border);
|
||||||
|
background: var(--button-outlined-warning-default-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-outlined-warning-default-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--button-outlined-warning-hovered-border);
|
||||||
|
background: var(--button-outlined-warning-hovered-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-outlined-warning-hovered-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
color: var(--button-outlined-warning-pressed-contrast);
|
||||||
|
border-color: var(--button-outlined-warning-pressed-border);
|
||||||
|
background: var(--button-outlined-warning-pressed-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-outlined-warning-pressed-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[styletype="text"] {
|
||||||
|
color: var(--button-text-warning-default-contrast);
|
||||||
|
border-color: var(--button-text-warning-default-border);
|
||||||
|
background: var(--button-text-warning-default-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-text-warning-default-contrast);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: var(--button-text-warning-hovered-contrast);
|
||||||
|
border-color: var(--button-text-warning-hovered-border);
|
||||||
|
background: var(--button-text-warning-hovered-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-text-warning-hovered-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
color: var(--button-text-warning-pressed-contrast);
|
||||||
|
border-color: var(--button-text-warning-pressed-border);
|
||||||
|
background: var(--button-text-warning-pressed-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--button-text-warning-pressed-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&[variant="info"] {
|
&[variant="info"] {
|
||||||
color: var(--color-info-700);
|
color: var(--button-contained-info-default-contrast);
|
||||||
background: var(--color-info-700);
|
border-color: var(--button-contained-info-default-border);
|
||||||
border-color: var(--color-info-700);
|
background: var(--button-contained-info-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-info-default-contrast);
|
stroke: var(--button-contained-info-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-info-900);
|
border-color: var(--button-contained-info-hovered-border);
|
||||||
border-color: var(--color-info-900);
|
background: var(--button-contained-info-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-info-hovered-contrast);
|
stroke: var(--button-contained-info-hovered-contrast);
|
||||||
@ -559,8 +585,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background: var(--color-info-950);
|
color: var(--button-contained-info-default-contrast);
|
||||||
border-color: var(--color-info-950);
|
border-color: var(--button-contained-info-pressed-border);
|
||||||
|
background: var(--button-contained-info-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-contained-info-pressed-contrast);
|
stroke: var(--button-contained-info-pressed-contrast);
|
||||||
@ -568,13 +595,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[styletype="outlined"] {
|
&[styletype="outlined"] {
|
||||||
|
color: var(--button-outlined-info-default-contrast);
|
||||||
|
border-color: var(--button-outlined-info-default-border);
|
||||||
|
background: var(--button-outlined-info-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-info-default-contrast);
|
stroke: var(--button-outlined-info-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-info-50);
|
border-color: var(--button-outlined-info-hovered-border);
|
||||||
border-color: var(--color-info-700);
|
background: var(--button-outlined-info-hovered-background);
|
||||||
color: var(--color-info-700);
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-info-hovered-contrast);
|
stroke: var(--button-outlined-info-hovered-contrast);
|
||||||
@ -583,9 +614,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--color-info-100);
|
color: var(--button-outlined-info-pressed-contrast);
|
||||||
color: var(--color-info-700);
|
border-color: var(--button-outlined-info-pressed-border);
|
||||||
border-color: var(--color-info-700);
|
background: var(--button-outlined-info-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-outlined-info-pressed-contrast);
|
stroke: var(--button-outlined-info-pressed-contrast);
|
||||||
@ -594,14 +625,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[styletype="text"] {
|
&[styletype="text"] {
|
||||||
|
color: var(--button-text-info-default-contrast);
|
||||||
|
border-color: var(--button-text-info-default-border);
|
||||||
|
background: var(--button-text-info-default-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-info-default-contrast);
|
stroke: var(--button-text-info-default-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent;
|
color: var(--button-text-info-hovered-contrast);
|
||||||
color: var(--color-info-900);
|
border-color: var(--button-text-info-hovered-border);
|
||||||
border-color: var(--color-info-900);
|
background: var(--button-text-info-hovered-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-info-hovered-contrast);
|
stroke: var(--button-text-info-hovered-contrast);
|
||||||
@ -610,9 +644,9 @@
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: transparent;
|
color: var(--button-text-info-pressed-contrast);
|
||||||
color: var(--color-info-950);
|
border-color: var(--button-text-info-pressed-border);
|
||||||
border-color: var(--color-info-950);
|
background: var(--button-text-info-pressed-background);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--button-text-info-pressed-contrast);
|
stroke: var(--button-text-info-pressed-contrast);
|
||||||
|
@ -4,11 +4,11 @@ import Tooltip from "../ToolTip";
|
|||||||
import Typography, { ETypo, ETypoColor } from "../Typography";
|
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { IOptionOld } from "../Form/SelectFieldOld";
|
import { IOption } from "../Form/SelectFieldOld";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
name?: string;
|
name?: string;
|
||||||
option: IOptionOld;
|
option: IOption;
|
||||||
toolTip?: string;
|
toolTip?: string;
|
||||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import Typography, { ETypo, ETypoColor } from "../Typography";
|
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
percentage: number;
|
percentage: number;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CircleProgress(props: IProps) {
|
export default function CircleProgress(props: IProps) {
|
||||||
const { percentage } = props;
|
const { percentage, className } = props;
|
||||||
|
|
||||||
const [animatedProgress, setAnimatedProgress] = useState(0);
|
const [animatedProgress, setAnimatedProgress] = useState(0);
|
||||||
const requestRef = useRef<number>();
|
const requestRef = useRef<number>();
|
||||||
@ -41,7 +43,7 @@ export default function CircleProgress(props: IProps) {
|
|||||||
const offset = circumference - (animatedProgress / 100) * circumference;
|
const offset = circumference - (animatedProgress / 100) * circumference;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes["root"]}>
|
<div className={classNames(classes["root"], className)}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="27" height="27" viewBox="0 0 27 27" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="27" height="27" viewBox="0 0 27 27" fill="none">
|
||||||
<circle className={classes["circleBackground"]} cx="13.5" cy="13.5" r={radius} strokeWidth="3" />
|
<circle className={classes["circleBackground"]} cx="13.5" cy="13.5" r={radius} strokeWidth="3" />
|
||||||
<circle
|
<circle
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
.container {
|
|
||||||
.root {
|
|
||||||
padding: 24px;
|
|
||||||
background-color: var(--color-generic-white);
|
|
||||||
border: 1px dashed #e7e7e7;
|
|
||||||
|
|
||||||
height: fit-content;
|
|
||||||
|
|
||||||
&[data-drag-over="true"] {
|
|
||||||
border: 1px dashed var(--color-neutral-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.validated {
|
|
||||||
border: 1px dashed var(--color-success-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
margin-right: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
background-color: #939393;
|
|
||||||
width: 1px;
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
margin-left: 18px;
|
|
||||||
|
|
||||||
.validated {
|
|
||||||
color: var(--color-success-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.refused-button {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-error-800);
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.documents-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
margin-top: 16px;
|
|
||||||
|
|
||||||
.file-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.left-part {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
.loader {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cross {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-container {
|
|
||||||
margin-top: 16px;
|
|
||||||
|
|
||||||
.add-button {
|
|
||||||
.add-document {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: var(--color-error-600);
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,452 +0,0 @@
|
|||||||
import DepositDocumentIcon from "@Assets/Icons/deposit-document.svg";
|
|
||||||
import PlusIcon from "@Assets/Icons/plus.svg";
|
|
||||||
import CrossIcon from "@Assets/Icons/cross.svg";
|
|
||||||
import DocumentCheckIcon from "@Assets/Icons/document-check.svg";
|
|
||||||
import Image from "next/image";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import Button, { EButtonstyletype, EButtonVariant } from "../Button";
|
|
||||||
import Tooltip from "../ToolTip";
|
|
||||||
import Typography, { ETypo, ETypoColor } from "../Typography";
|
|
||||||
import classes from "./classes.module.scss";
|
|
||||||
import { Document, DocumentHistory, File as FileCustomer } from "le-coffre-resources/dist/Customer";
|
|
||||||
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
|
|
||||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import Confirm from "../OldModal/Confirm";
|
|
||||||
import Alert from "../OldModal/Alert";
|
|
||||||
import GreenCheckIcon from "@Assets/Icons/green-check.svg";
|
|
||||||
import Loader from "../Loader";
|
|
||||||
import TextAreaField from "../Form/TextareaField";
|
|
||||||
import { toast } from "react-toastify";
|
|
||||||
|
|
||||||
type IProps = {
|
|
||||||
defaultFiles?: FileCustomer[];
|
|
||||||
onChange?: (files: File[]) => void;
|
|
||||||
document: Document;
|
|
||||||
};
|
|
||||||
|
|
||||||
type IFile = {
|
|
||||||
index: number;
|
|
||||||
file: File;
|
|
||||||
uid: string;
|
|
||||||
archived: Date | null;
|
|
||||||
fileName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type IState = {
|
|
||||||
files: IFile[];
|
|
||||||
isDragOver: boolean;
|
|
||||||
currentFiles?: FileCustomer[];
|
|
||||||
refusedReason?: string;
|
|
||||||
isShowRefusedReasonModalVisible: boolean;
|
|
||||||
showFailedUploaded: string | null;
|
|
||||||
loading: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type fileAccepted = {
|
|
||||||
extension: string;
|
|
||||||
size: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filesAccepted: { [key: string]: fileAccepted } = {
|
|
||||||
"application/pdf": {
|
|
||||||
extension: "pdf",
|
|
||||||
size: 41943040,
|
|
||||||
},
|
|
||||||
"image/jpeg": {
|
|
||||||
extension: "jpeg",
|
|
||||||
size: 41943040,
|
|
||||||
},
|
|
||||||
"image/png": {
|
|
||||||
extension: "png",
|
|
||||||
size: 41943040,
|
|
||||||
},
|
|
||||||
"image/jpg": {
|
|
||||||
extension: "jpg",
|
|
||||||
size: 41943040,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class DepositDocument extends React.Component<IProps, IState> {
|
|
||||||
private inputRef = React.createRef<HTMLInputElement>();
|
|
||||||
private index = 0;
|
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
files: [],
|
|
||||||
isDragOver: false,
|
|
||||||
currentFiles: this.props.defaultFiles,
|
|
||||||
refusedReason: "",
|
|
||||||
isShowRefusedReasonModalVisible: false,
|
|
||||||
showFailedUploaded: null,
|
|
||||||
loading: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addDocument = this.addDocument.bind(this);
|
|
||||||
this.onFileChange = this.onFileChange.bind(this);
|
|
||||||
this.addFile = this.addFile.bind(this);
|
|
||||||
this.removeFile = this.removeFile.bind(this);
|
|
||||||
this.onDragOver = this.onDragOver.bind(this);
|
|
||||||
this.onDragDrop = this.onDragDrop.bind(this);
|
|
||||||
this.onDragLeave = this.onDragLeave.bind(this);
|
|
||||||
this.onCloseModalShowRefusedReason = this.onCloseModalShowRefusedReason.bind(this);
|
|
||||||
this.onOpenModalShowRefusedReason = this.onOpenModalShowRefusedReason.bind(this);
|
|
||||||
this.showRefusedReason = this.showRefusedReason.bind(this);
|
|
||||||
this.onCloseAlertUpload = this.onCloseAlertUpload.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override render(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className={classes["container"]}>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
classes["root"],
|
|
||||||
this.props.document.document_status === EDocumentStatus.VALIDATED && classes["validated"],
|
|
||||||
)}
|
|
||||||
onDragOver={this.onDragOver}
|
|
||||||
onDrop={this.onDragDrop}
|
|
||||||
onDragLeave={this.onDragLeave}
|
|
||||||
data-drag-over={this.state.isDragOver.toString()}>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref={this.inputRef}
|
|
||||||
hidden
|
|
||||||
onChange={this.onFileChange}
|
|
||||||
accept={Object.keys(filesAccepted).join(",")}
|
|
||||||
/>
|
|
||||||
<div className={classes["top-container"]}>
|
|
||||||
<div className={classes["left"]}>
|
|
||||||
<Image src={DepositDocumentIcon} alt="Deposit document" />
|
|
||||||
</div>
|
|
||||||
<div className={classes["separator"]} />
|
|
||||||
<div className={classes["right"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.COLOR_GENERIC_BLACK} className={classes["title"]}>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
this.props.document.document_status === EDocumentStatus.VALIDATED ? classes["validated"] : ""
|
|
||||||
}>
|
|
||||||
{this.props.document.document_type?.name}
|
|
||||||
</div>
|
|
||||||
{this.props.document.document_type?.public_description !== " " &&
|
|
||||||
this.props.document.document_type?.public_description !== "" &&
|
|
||||||
this.props.document.document_status !== EDocumentStatus.VALIDATED && (
|
|
||||||
<Tooltip text={this.props.document.document_type?.public_description || ""} />
|
|
||||||
)}
|
|
||||||
{this.props.document.document_status === EDocumentStatus.VALIDATED && (
|
|
||||||
<Image src={GreenCheckIcon} alt="Document check" />
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
{this.props.document.document_status !== EDocumentStatus.VALIDATED && (
|
|
||||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
|
||||||
Sélectionnez des documents .jpg, .pdf ou .png
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{this.props.document.document_history?.map((history) => (
|
|
||||||
<div key={history.uid}>{this.renderDocumentHistory(history)}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={classes["documents-container"]}>
|
|
||||||
{this.state.files.map((file) => {
|
|
||||||
const fileObj = file.file;
|
|
||||||
if (file.archived) return;
|
|
||||||
return (
|
|
||||||
<div className={classes["file-container"]} key={fileObj.name + file.index}>
|
|
||||||
<div className={classes["left-part"]}>
|
|
||||||
<Image src={DocumentCheckIcon} alt="Document check" />
|
|
||||||
<Typography
|
|
||||||
typo={ETypo.TEXT_MD_REGULAR}
|
|
||||||
color={ETypoColor.COLOR_NEUTRAL_500}
|
|
||||||
title={file.fileName ?? fileObj.name}>
|
|
||||||
{this.shortName(file.fileName || fileObj.name)}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
<Image
|
|
||||||
src={CrossIcon}
|
|
||||||
alt="Cross icon"
|
|
||||||
className={classes["cross"]}
|
|
||||||
onClick={this.removeFile}
|
|
||||||
data-file={file.index}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{this.state.loading && (
|
|
||||||
<div className={classes["file-container"]}>
|
|
||||||
<div className={classes["left-part"]}>
|
|
||||||
<div className={classes["loader"]}>
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
|
|
||||||
Chargement...
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{this.props.document.document_status !== EDocumentStatus.VALIDATED && (
|
|
||||||
<div className={classes["bottom-container"]}>
|
|
||||||
<Button
|
|
||||||
variant={EButtonVariant.PRIMARY}
|
|
||||||
styletype={EButtonstyletype.TEXT}
|
|
||||||
className={classes["add-button"]}
|
|
||||||
onClick={this.addDocument}>
|
|
||||||
<Typography
|
|
||||||
typo={ETypo.TEXT_MD_SEMIBOLD}
|
|
||||||
color={ETypoColor.COLOR_SECONDARY_500}
|
|
||||||
className={classes["add-document"]}>
|
|
||||||
Ajouter un document <Image src={PlusIcon} alt="Plus icon" />
|
|
||||||
</Typography>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Confirm
|
|
||||||
isOpen={this.state.isShowRefusedReasonModalVisible}
|
|
||||||
onClose={this.onCloseModalShowRefusedReason}
|
|
||||||
showCancelButton={false}
|
|
||||||
onAccept={this.onCloseModalShowRefusedReason}
|
|
||||||
closeBtn
|
|
||||||
header={"Motif du refus"}
|
|
||||||
confirmText={"J'ai compris"}>
|
|
||||||
<div className={classes["modal-content"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
Votre document a été refusé pour la raison suivante :
|
|
||||||
</Typography>
|
|
||||||
<TextAreaField placeholder="Description" defaultValue={this.state.refusedReason} readonly />
|
|
||||||
</div>
|
|
||||||
</Confirm>
|
|
||||||
</div>
|
|
||||||
{this.props.document.document_status === EDocumentStatus.REFUSED && (
|
|
||||||
<Typography typo={ETypo.TEXT_SM_REGULAR} className={classes["error-message"]}>
|
|
||||||
Ce document n'est pas conforme. Veuillez le déposer à nouveau.
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{this.state.showFailedUploaded && (
|
|
||||||
<Alert onClose={this.onCloseAlertUpload} header={"Fichier non autorisé"} isOpen={!!this.state.showFailedUploaded}>
|
|
||||||
<div className={classes["modal-content"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
{this.state.showFailedUploaded}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override componentDidMount(): void {
|
|
||||||
if (this.props.defaultFiles) {
|
|
||||||
this.setState({
|
|
||||||
files: this.props.defaultFiles.map((file) => ({
|
|
||||||
index: this.index++,
|
|
||||||
file: new File([""], file.file_path ?? "", {}),
|
|
||||||
uid: file.uid!,
|
|
||||||
fileName: file.file_name,
|
|
||||||
archived: file.archived_at ? new Date(file.archived_at) : null,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private openSuccessToast() {
|
|
||||||
toast.success("Document envoyé avec succès");
|
|
||||||
}
|
|
||||||
|
|
||||||
private onCloseModalShowRefusedReason() {
|
|
||||||
this.setState({
|
|
||||||
isShowRefusedReasonModalVisible: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private onOpenModalShowRefusedReason() {
|
|
||||||
this.setState({
|
|
||||||
isShowRefusedReasonModalVisible: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderDocumentHistory(history: DocumentHistory): JSX.Element | null {
|
|
||||||
switch (history.document_status) {
|
|
||||||
case EDocumentStatus.ASKED:
|
|
||||||
return (
|
|
||||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
|
||||||
Demandé par votre notaire le {this.formatDate(history.created_at!)}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
case EDocumentStatus.VALIDATED:
|
|
||||||
return (
|
|
||||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
|
||||||
Validé par votre notaire le {this.formatDate(history.created_at!)}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
case EDocumentStatus.DEPOSITED:
|
|
||||||
return (
|
|
||||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
|
||||||
Déposé le {this.formatDate(history.created_at!)}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
|
|
||||||
case EDocumentStatus.REFUSED:
|
|
||||||
return (
|
|
||||||
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_ERROR_800}>
|
|
||||||
Document non conforme
|
|
||||||
{history.refused_reason && history.refused_reason.length > 0 && (
|
|
||||||
<Button
|
|
||||||
variant={EButtonVariant.PRIMARY}
|
|
||||||
styletype={EButtonstyletype.TEXT}
|
|
||||||
className={classes["refused-button"]}
|
|
||||||
onClick={() => this.showRefusedReason(history.refused_reason ?? "")}>
|
|
||||||
Voir le motif de refus
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private shortName(name: string): string {
|
|
||||||
const maxLength = 20;
|
|
||||||
if (name.length > maxLength) {
|
|
||||||
return name.substring(0, maxLength / 2) + "..." + name.substring(name.length - maxLength / 2, name.length);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onDragOver(event: React.DragEvent<HTMLDivElement>) {
|
|
||||||
if (!this.state.isDragOver) {
|
|
||||||
this.setState({
|
|
||||||
isDragOver: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
private showRefusedReason(refusedReason: string) {
|
|
||||||
this.setState({
|
|
||||||
refusedReason,
|
|
||||||
});
|
|
||||||
this.onOpenModalShowRefusedReason();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onDragLeave(event: React.DragEvent<HTMLDivElement>) {
|
|
||||||
this.setState({
|
|
||||||
isDragOver: false,
|
|
||||||
});
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async onDragDrop(event: React.DragEvent<HTMLDivElement>) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.setState({
|
|
||||||
isDragOver: false,
|
|
||||||
});
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
if (file) this.addFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async addFile(file: File) {
|
|
||||||
const fileAccepted = filesAccepted[file.type];
|
|
||||||
if (!fileAccepted) {
|
|
||||||
alert("Le fichier déposé doit être au format .jpg .pdf .jpeg ou .png");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (file.size > fileAccepted.size) {
|
|
||||||
alert("Le fichier est trop volumineux et ne doit pas dépasser 32mo");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
loading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", file, file.name);
|
|
||||||
const query = JSON.stringify({ document: { uid: this.props.document.uid } });
|
|
||||||
formData.append("q", query);
|
|
||||||
|
|
||||||
let newFile: FileCustomer;
|
|
||||||
try {
|
|
||||||
newFile = await Files.getInstance().post(formData);
|
|
||||||
} catch (e) {
|
|
||||||
this.setState({ showFailedUploaded: "Le fichier ne correspond pas aux critères demandés", loading: false });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile];
|
|
||||||
|
|
||||||
const newFileList = [
|
|
||||||
...this.state.files,
|
|
||||||
{
|
|
||||||
index: this.index++,
|
|
||||||
file: file,
|
|
||||||
uid: newFile.uid!,
|
|
||||||
archived: null,
|
|
||||||
fileName: newFile?.file_name ?? "",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
this.openSuccessToast();
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
currentFiles: files,
|
|
||||||
loading: false,
|
|
||||||
files: newFileList,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (this.props.onChange) this.props.onChange(newFileList.map((file) => file.file));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async removeFile(e: any) {
|
|
||||||
const image = e.target as HTMLElement;
|
|
||||||
const indexToRemove = image.getAttribute("data-file");
|
|
||||||
if (!indexToRemove) return;
|
|
||||||
const file = this.state.files.find((file) => file.index === parseInt(indexToRemove));
|
|
||||||
if (!file) return;
|
|
||||||
this.setState({
|
|
||||||
files: this.state.files.filter((file) => file.index !== parseInt(indexToRemove)),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file));
|
|
||||||
await Files.getInstance().delete(file.uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async onFileChange() {
|
|
||||||
if (!this.inputRef.current) return;
|
|
||||||
const files = this.inputRef.current.files;
|
|
||||||
if (!files) {
|
|
||||||
this.setState({ loading: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const file = files[0];
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (file) {
|
|
||||||
await this.setState({ loading: true }, () => {
|
|
||||||
this.addFile(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.setState({ loading: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onCloseAlertUpload() {
|
|
||||||
this.setState({ showFailedUploaded: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
private addDocument() {
|
|
||||||
if (!this.inputRef.current) return;
|
|
||||||
this.inputRef.current.value = "";
|
|
||||||
this.inputRef.current.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatDate(date: Date) {
|
|
||||||
const dateToConvert = new Date(date);
|
|
||||||
return dateToConvert.toLocaleDateString("fr-FR");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,38 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 277px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
min-width: var(--spacing-3, 24px);
|
||||||
|
min-height: var(--spacing-3, 24px);
|
||||||
|
width: var(--spacing-3, 24px);
|
||||||
|
height: var(--spacing-3, 24px);
|
||||||
|
stroke: var(--color-primary-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
min-width: var(--spacing-3, 24px);
|
||||||
|
min-height: var(--spacing-3, 24px);
|
||||||
|
width: var(--spacing-3, 24px);
|
||||||
|
height: var(--spacing-3, 24px);
|
||||||
|
stroke: var(--color-error-500);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import { CheckCircleIcon, XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import IconButton, { EIconButtonVariant } from "../../IconButton";
|
||||||
|
import Loader from "../../Loader";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
file: File | null;
|
||||||
|
onRemove: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DocumentFileElement(props: IProps) {
|
||||||
|
const { isLoading, onRemove, file, error } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<div className={classes["content"]}>
|
||||||
|
{isLoading ? <Loader /> : !error ? <CheckCircleIcon /> : <XCircleIcon className={classes["error"]} />}
|
||||||
|
{error && (
|
||||||
|
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.COLOR_ERROR_500} className={classes["file-name"]}>
|
||||||
|
{error}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{file && !error && (
|
||||||
|
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_SECONDARY} className={classes["file-name"]}>
|
||||||
|
{file.name}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<IconButton onClick={onRemove} icon={<XMarkIcon />} variant={error ? EIconButtonVariant.ERROR : EIconButtonVariant.NEUTRAL} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-3, 24px);
|
||||||
|
|
||||||
|
width: fit-content;
|
||||||
|
padding: var(--spacing-2, 16px) var(--Radius-2xl, 32px) var(--spacing-2, 16px) var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
clip-path: inset(0 round var(--Radius-md, 8px));
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: -15px;
|
||||||
|
top: -15px;
|
||||||
|
right: -15px;
|
||||||
|
bottom: -15px;
|
||||||
|
border: 16px dashed var(--dropdown-input-border-hovered);
|
||||||
|
border-radius: calc(2 * var(--Radius-md, 8px));
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.filled {
|
||||||
|
&::before {
|
||||||
|
border-color: var(--dropdown-input-border-expanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::before {
|
||||||
|
border-color: var(--dropdown-input-border-expanded);
|
||||||
|
}
|
||||||
|
background: var(--primary-weak-higlight, #e5eefa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-4, 32px);
|
||||||
|
|
||||||
|
.browse-document-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
|
||||||
|
.browse-document {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
|
||||||
|
&.desktop {
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobile {
|
||||||
|
display: none;
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.documents {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
min-width: var(--spacing-3, 24px);
|
||||||
|
min-height: var(--spacing-3, 24px);
|
||||||
|
width: var(--spacing-3, 24px);
|
||||||
|
height: var(--spacing-3, 24px);
|
||||||
|
stroke: var(--color-primary-500);
|
||||||
|
}
|
||||||
|
}
|
219
src/front/Components/DesignSystem/DragAndDrop/index.tsx
Normal file
219
src/front/Components/DesignSystem/DragAndDrop/index.tsx
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import { DocumentPlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "../Button";
|
||||||
|
import Separator, { ESeperatorColor, ESeperatorDirection } from "../Separator";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import DocumentFileElement from "./DocumentFileElement";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Drag and drop component to upload files
|
||||||
|
* @param {string} title - Title of the component
|
||||||
|
* @param {string} description - Description of the component
|
||||||
|
* @param {IDocumentFileWithUid[]} defaultFiles - Default files to display
|
||||||
|
* @param {(fileUid: string) => Promise<any>} onDelete - Function to delete a file (must be used with defaultFiles)
|
||||||
|
* @param {(file: File) => Promise<any>} onAddFile - Function to add a file (must be used with defaultFiles)
|
||||||
|
*/
|
||||||
|
type IProps = {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
defaultFiles?: IDocumentFileWithUid[];
|
||||||
|
onDelete?: (fileUid: string) => Promise<any>;
|
||||||
|
onAddFile?: (file: File) => Promise<any>;
|
||||||
|
name?: string;
|
||||||
|
onChange?: (files: File[]) => void;
|
||||||
|
} & (
|
||||||
|
| { onDelete: (fileUid: string) => Promise<any>; onAddFile?: never; defaultFiles: IDocumentFileWithUid[] }
|
||||||
|
| { onDelete?: never; onAddFile: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] }
|
||||||
|
| { onDelete?: (fileUid: string) => Promise<any>; onAddFile?: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] }
|
||||||
|
| { onDelete?: never; onAddFile?: never; defaultFiles?: never }
|
||||||
|
);
|
||||||
|
|
||||||
|
type IMimeTypes = {
|
||||||
|
extension: string;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mimeTypesAccepted: { [key: string]: IMimeTypes } = {
|
||||||
|
"application/pdf": {
|
||||||
|
extension: "pdf",
|
||||||
|
size: 41943040,
|
||||||
|
},
|
||||||
|
"image/jpeg": {
|
||||||
|
extension: "jpeg",
|
||||||
|
size: 41943040,
|
||||||
|
},
|
||||||
|
"image/png": {
|
||||||
|
extension: "png",
|
||||||
|
size: 41943040,
|
||||||
|
},
|
||||||
|
"image/jpg": {
|
||||||
|
extension: "jpg",
|
||||||
|
size: 41943040,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type IDocumentFileBase = {
|
||||||
|
id: string;
|
||||||
|
file: File | null;
|
||||||
|
uid?: string;
|
||||||
|
isLoading?: boolean;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IDocumentFileWithUid = IDocumentFileBase & {
|
||||||
|
uid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type IDocumentFile = IDocumentFileBase | IDocumentFileWithUid;
|
||||||
|
|
||||||
|
export default function DragAndDrop(props: IProps) {
|
||||||
|
const { title, description, defaultFiles, onDelete, onAddFile, name, onChange } = props;
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const [documentFiles, setDocumentFiles] = useState<IDocumentFile[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultFiles) {
|
||||||
|
setDocumentFiles(defaultFiles);
|
||||||
|
}
|
||||||
|
}, [defaultFiles]);
|
||||||
|
|
||||||
|
useEffect(() => onChange?.(documentFiles.map((doc) => doc.file).filter((file) => file !== null) as File[]), [documentFiles, onChange]);
|
||||||
|
|
||||||
|
const handleAddFiles = useCallback(
|
||||||
|
(files: File[]) => {
|
||||||
|
files.forEach((file) => {
|
||||||
|
setDocumentFiles((prevDocs) => [...prevDocs, { id: file.name, file: file, isLoading: true }]);
|
||||||
|
try {
|
||||||
|
if (!mimeTypesAccepted[file.type]) {
|
||||||
|
throw new Error("Type de fichier non accepté");
|
||||||
|
}
|
||||||
|
const newDoc: IDocumentFile = { id: file.name, file, isLoading: false };
|
||||||
|
|
||||||
|
if (onAddFile) {
|
||||||
|
// As onAddFile is used along defaultFiles prop we dont need to update the state here but the parent component should update the defaultFiles prop
|
||||||
|
return onAddFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return setTimeout(async () => {
|
||||||
|
setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc)));
|
||||||
|
}, 1000);
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorDoc: IDocumentFile = { id: file.name, file: null, isLoading: false, error: error.message };
|
||||||
|
return setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === errorDoc.id ? errorDoc : doc)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[onAddFile],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDrop = useCallback(
|
||||||
|
(event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const files = Array.from(event.dataTransfer.files);
|
||||||
|
handleAddFiles(files);
|
||||||
|
},
|
||||||
|
[handleAddFiles],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemove = useCallback(
|
||||||
|
(documentFile: IDocumentFile) => {
|
||||||
|
const loadingDoc = { ...documentFile, isLoading: true };
|
||||||
|
setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === documentFile.id ? loadingDoc : doc)));
|
||||||
|
|
||||||
|
if (documentFile.uid) {
|
||||||
|
return onDelete?.(documentFile.uid);
|
||||||
|
}
|
||||||
|
return setDocumentFiles((prevDocs) => prevDocs.filter((doc) => doc.id !== documentFile.id));
|
||||||
|
},
|
||||||
|
[onDelete],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBrowse = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const files = Array.from(event.target.files || []);
|
||||||
|
handleAddFiles(files);
|
||||||
|
},
|
||||||
|
[handleAddFiles],
|
||||||
|
);
|
||||||
|
|
||||||
|
const triggerFileInput = () => {
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(classes["root"], documentFiles.length > 0 && classes["filled"])}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragOver={(e) => e.preventDefault()}>
|
||||||
|
<div className={classes["content"]}>
|
||||||
|
<DocumentPlusIcon />
|
||||||
|
<Separator direction={ESeperatorDirection.VERTICAL} color={ESeperatorColor.STRONG} size={64} />
|
||||||
|
<div className={classes["browse-document-container"]}>
|
||||||
|
<div className={classNames(classes["browse-document"], classes["desktop"])}>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant={EButtonVariant.PRIMARY}
|
||||||
|
styletype={EButtonstyletype.TEXT}
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={triggerFileInput}>
|
||||||
|
{inputFile()}
|
||||||
|
parcourir
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classNames(classes["browse-document"], classes["mobile"])}>
|
||||||
|
<Button
|
||||||
|
variant={EButtonVariant.PRIMARY}
|
||||||
|
styletype={EButtonstyletype.TEXT}
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={triggerFileInput}>
|
||||||
|
{inputFile()}
|
||||||
|
Ajouter un document
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{description && (
|
||||||
|
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{documentFiles.length > 0 && (
|
||||||
|
<div className={classes["documents"]}>
|
||||||
|
{documentFiles.map((documentFile) => (
|
||||||
|
<DocumentFileElement
|
||||||
|
key={documentFile.id}
|
||||||
|
isLoading={documentFile.isLoading}
|
||||||
|
file={documentFile.file}
|
||||||
|
onRemove={() => handleRemove(documentFile)}
|
||||||
|
error={documentFile.error}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function inputFile() {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
name={name}
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
accept={Object.keys(mimeTypesAccepted).join(",")}
|
||||||
|
onChange={handleBrowse}
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,11 @@ type IProps = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onSelectionChange?: (option: IOption) => void;
|
onSelectionChange?: (option: IOption) => void;
|
||||||
selectedOption?: IOption | null;
|
selectedOption?: IOption | null;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Dropdown(props: IProps) {
|
export default function Dropdown(props: IProps) {
|
||||||
const { options, placeholder, disabled, onSelectionChange, selectedOption: selectedOptionProps, label } = props;
|
const { options, placeholder, disabled, onSelectionChange, selectedOption: selectedOptionProps, label, className } = props;
|
||||||
const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null);
|
const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null);
|
||||||
const openable = useOpenable({ defaultOpen: false });
|
const openable = useOpenable({ defaultOpen: false });
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ export default function Dropdown(props: IProps) {
|
|||||||
openable={openable}
|
openable={openable}
|
||||||
onSelect={handleOnSelect}
|
onSelect={handleOnSelect}
|
||||||
selectedOptions={selectedOption ? [selectedOption] : []}>
|
selectedOptions={selectedOption ? [selectedOption] : []}>
|
||||||
<div className={classes["root"]}>
|
<div className={classNames(classes["root"], className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
||||||
{label}
|
{label}
|
||||||
|
@ -34,6 +34,8 @@ export default class AutocompleteField extends BaseField<IProps, IState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public override componentDidUpdate(prevProps: IProps): void {
|
public override componentDidUpdate(prevProps: IProps): void {
|
||||||
|
super.componentDidUpdate(prevProps);
|
||||||
|
|
||||||
if (prevProps.selectedOption !== this.props.selectedOption) {
|
if (prevProps.selectedOption !== this.props.selectedOption) {
|
||||||
this.setState({ selectedOption: this.props.selectedOption ?? null });
|
this.setState({ selectedOption: this.props.selectedOption ?? null });
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ export default class AutocompleteMultiSelectField extends BaseField<IProps, ISta
|
|||||||
};
|
};
|
||||||
|
|
||||||
public override componentDidUpdate(prevProps: IProps): void {
|
public override componentDidUpdate(prevProps: IProps): void {
|
||||||
|
super.componentDidUpdate(prevProps);
|
||||||
|
|
||||||
if (prevProps.selectedOptions !== this.props.selectedOptions) {
|
if (prevProps.selectedOptions !== this.props.selectedOptions) {
|
||||||
this.setState({ selectedOptions: this.props.selectedOptions ?? null });
|
this.setState({ selectedOptions: this.props.selectedOptions ?? null });
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,9 @@ export default class SelectField extends BaseField<IProps, IState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public override componentDidUpdate(prevProps: IProps): void {
|
public override componentDidUpdate(prevProps: IProps): void {
|
||||||
|
super.componentDidUpdate(prevProps);
|
||||||
|
|
||||||
|
|
||||||
if (prevProps.selectedOption !== this.props.selectedOption) {
|
if (prevProps.selectedOption !== this.props.selectedOption) {
|
||||||
this.setState({ selectedOption: this.props.selectedOption ?? null });
|
this.setState({ selectedOption: this.props.selectedOption ?? null });
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ import classes from "./classes.module.scss";
|
|||||||
import { NextRouter, useRouter } from "next/router";
|
import { NextRouter, useRouter } from "next/router";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
selectedOption?: IOptionOld;
|
selectedOption?: IOption;
|
||||||
onChange?: (selectedOption: IOptionOld) => void;
|
onChange?: (selectedOption: IOption) => void;
|
||||||
options: IOptionOld[];
|
options: IOption[];
|
||||||
hasBorderRightCollapsed?: boolean;
|
hasBorderRightCollapsed?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -21,7 +21,7 @@ type IProps = {
|
|||||||
errors?: ValidationError;
|
errors?: ValidationError;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IOptionOld = {
|
export type IOption = {
|
||||||
value: unknown;
|
value: unknown;
|
||||||
label: string;
|
label: string;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
@ -32,7 +32,7 @@ type IState = {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
listWidth: number;
|
listWidth: number;
|
||||||
listHeight: number;
|
listHeight: number;
|
||||||
selectedOption: IOptionOld | null;
|
selectedOption: IOption | null;
|
||||||
errors: ValidationError | null;
|
errors: ValidationError | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -187,7 +187,7 @@ class SelectFieldClass extends React.Component<IPropsClass, IState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSelect(option: IOptionOld, e: React.MouseEvent<HTMLLIElement, MouseEvent>) {
|
private onSelect(option: IOption, e: React.MouseEvent<HTMLLIElement, MouseEvent>) {
|
||||||
if (this.props.disabled) return;
|
if (this.props.disabled) return;
|
||||||
this.props.onChange && this.props.onChange(option);
|
this.props.onChange && this.props.onChange(option);
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
.root {
|
.root {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
&[data-is-disabled="true"] {
|
&[data-is-disabled="true"] {
|
||||||
opacity: var(--opacity-disabled, 0.3);
|
opacity: var(--opacity-disabled, 0.3);
|
||||||
.input-container {
|
.input-container {
|
||||||
@ -27,6 +28,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: var(--spacing-2, 16px) var(--spacing-sm, 8px);
|
padding: var(--spacing-2, 16px) var(--spacing-sm, 8px);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -5,6 +5,9 @@ import BaseField, { IProps as IBaseFieldProps } from "../BaseField";
|
|||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import { XMarkIcon, Square2StackIcon } from "@heroicons/react/24/outline";
|
import { XMarkIcon, Square2StackIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||||
|
|
||||||
|
|
||||||
export type IProps = IBaseFieldProps & {
|
export type IProps = IBaseFieldProps & {
|
||||||
canCopy?: boolean;
|
canCopy?: boolean;
|
||||||
password?: boolean;
|
password?: boolean;
|
||||||
@ -36,6 +39,7 @@ export default class TextField extends BaseField<IProps> {
|
|||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
name={this.props.name}
|
name={this.props.name}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
|
readOnly={this.props.readonly}
|
||||||
type={this.props.password ? "password" : "text"}
|
type={this.props.password ? "password" : "text"}
|
||||||
/>
|
/>
|
||||||
{this.props.canCopy && !this.hasError() && (
|
{this.props.canCopy && !this.hasError() && (
|
||||||
@ -58,6 +62,7 @@ export default class TextField extends BaseField<IProps> {
|
|||||||
private onCopyClick = (): void => {
|
private onCopyClick = (): void => {
|
||||||
if (this.props.canCopy) {
|
if (this.props.canCopy) {
|
||||||
navigator.clipboard.writeText(this.state.value ?? "");
|
navigator.clipboard.writeText(this.state.value ?? "");
|
||||||
|
ToasterService.getInstance().success({ title: "Copié avec succès !", description: this.state.value });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import classes from "./classes.module.scss";
|
|||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
width?: string | number;
|
||||||
|
color?: string;
|
||||||
}
|
}
|
||||||
export default class Loader extends React.Component<IProps> {
|
export default class Loader extends React.Component<IProps> {
|
||||||
public override render(): JSX.Element {
|
public override render(): JSX.Element {
|
||||||
return <ArrowPathIcon className={classes["root"]} />;
|
return <ArrowPathIcon className={classes["root"]} color={this.props.color} width={this.props.width} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 3;
|
z-index: 5;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -54,7 +54,8 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
opacity: 0.3;
|
||||||
|
background: var(--primary-default-deep, #013391);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,4 +75,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-fullwidth="true"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ export type ISearchBlockListProps = {
|
|||||||
text: string;
|
text: string;
|
||||||
link: string;
|
link: string;
|
||||||
};
|
};
|
||||||
|
fullwidth?: boolean;
|
||||||
};
|
};
|
||||||
export default function SearchBlockList(props: ISearchBlockListProps) {
|
export default function SearchBlockList(props: ISearchBlockListProps) {
|
||||||
const { blocks, onSelectedBlock, bottomButton } = props;
|
const { blocks, onSelectedBlock, bottomButton, fullwidth = false } = props;
|
||||||
|
|
||||||
const [selectedBlock, setSelectedBlock] = useState<IBlock | null>(null);
|
const [selectedBlock, setSelectedBlock] = useState<IBlock | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -69,7 +70,7 @@ export default function SearchBlockList(props: ISearchBlockListProps) {
|
|||||||
}, [blocks]);
|
}, [blocks]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]} data-fullwidth={fullwidth}>
|
||||||
<div className={classes["searchbar"]} ref={searchBarRef}>
|
<div className={classes["searchbar"]} ref={searchBarRef}>
|
||||||
<SearchBar placeholder="Chercher" onChange={handleSearchChange} />
|
<SearchBar placeholder="Chercher" onChange={handleSearchChange} />
|
||||||
{bottomButton && (
|
{bottomButton && (
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--separator-stroke-default);
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background-color: var(--separator-stroke-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.strong {
|
||||||
|
background-color: var(--separator-stroke-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.contrast {
|
||||||
|
background-color: var(--separator-stroke-contrast);
|
||||||
|
}
|
||||||
|
}
|
35
src/front/Components/DesignSystem/Separator/index.tsx
Normal file
35
src/front/Components/DesignSystem/Separator/index.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
export enum ESeperatorColor {
|
||||||
|
LIGHT = "light",
|
||||||
|
DEFAULT = "default",
|
||||||
|
STRONG = "strong",
|
||||||
|
CONTRAST = "contrast",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ESeperatorDirection {
|
||||||
|
HORIZONTAL = "horizontal",
|
||||||
|
VERTICAL = "vertical",
|
||||||
|
}
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
color?: ESeperatorColor;
|
||||||
|
direction?: ESeperatorDirection;
|
||||||
|
size?: number;
|
||||||
|
thickness?: number;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Separator(props: IProps) {
|
||||||
|
const { color = ESeperatorColor.DEFAULT, direction = ESeperatorDirection.HORIZONTAL, size, thickness = 1, className } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(classes["root"], classes[color], classes[direction], className)}
|
||||||
|
style={direction === ESeperatorDirection.HORIZONTAL ? { width: size, height: thickness } : { height: size, width: thickness }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -8,8 +8,9 @@ import TableRow from "@mui/material/TableRow";
|
|||||||
|
|
||||||
import Typography, { ETypo, ETypoColor } from "../../Typography";
|
import Typography, { ETypo, ETypoColor } from "../../Typography";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
|
import { SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
export type IRowProps = { key: string } & Record<string, React.ReactNode>;
|
export type IRowProps = { key: string } & Record<string, React.ReactNode | { sx: SxProps<Theme>; content: React.ReactNode }>;
|
||||||
|
|
||||||
type IRow = {
|
type IRow = {
|
||||||
key?: string;
|
key?: string;
|
||||||
@ -29,7 +30,7 @@ export type IHead = {
|
|||||||
|
|
||||||
type CellContent = {
|
type CellContent = {
|
||||||
key: string;
|
key: string;
|
||||||
value: React.ReactNode;
|
value: React.ReactNode | { sx: SxProps<Theme>; content: React.ReactNode };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MuiTable(props: IProps) {
|
export default function MuiTable(props: IProps) {
|
||||||
@ -82,12 +83,14 @@ export default function MuiTable(props: IProps) {
|
|||||||
className={classes["cell"]}
|
className={classes["cell"]}
|
||||||
key={cell.key}
|
key={cell.key}
|
||||||
align="left"
|
align="left"
|
||||||
sx={{ border: 0, padding: "4px 8px", height: "53px" }}>
|
sx={{ ...getCellValueStyle(cell.value), border: 0, padding: "4px 8px", height: "53px" }}>
|
||||||
<Typography
|
<Typography
|
||||||
className={classes["content"]}
|
className={classes["content"]}
|
||||||
typo={ETypo.TEXT_MD_REGULAR}
|
typo={ETypo.TEXT_MD_REGULAR}
|
||||||
color={ETypoColor.COLOR_NEUTRAL_900}>
|
color={ETypoColor.COLOR_NEUTRAL_900}>
|
||||||
{cell.value}
|
{cell.value && typeof cell.value === "object" && "content" in cell.value
|
||||||
|
? cell.value.content
|
||||||
|
: cell.value}
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
@ -99,4 +102,11 @@ export default function MuiTable(props: IProps) {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function getCellValueStyle(value: React.ReactNode | { sx: SxProps<Theme>; content: React.ReactNode }) {
|
||||||
|
if (typeof value === "object" && value !== null && "sx" in value) {
|
||||||
|
return value.sx;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&.info {
|
&.info {
|
||||||
background-color: var(--tag-info-background);
|
background-color: var(--tag-info-background);
|
||||||
}
|
}
|
||||||
@ -24,4 +26,8 @@
|
|||||||
&.error {
|
&.error {
|
||||||
background-color: var(--tag-error-background);
|
background-color: var(--tag-error-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.neutral {
|
||||||
|
background-color: var(--tag-neutral-background);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ export enum ETagColor {
|
|||||||
SUCCESS = "success",
|
SUCCESS = "success",
|
||||||
ERROR = "error",
|
ERROR = "error",
|
||||||
WARNING = "warning",
|
WARNING = "warning",
|
||||||
|
NEUTRAL = "neutral",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ETagVariant {
|
export enum ETagVariant {
|
||||||
@ -24,10 +25,11 @@ type IProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const colorMap: Record<ETagColor, ETypoColor> = {
|
const colorMap: Record<ETagColor, ETypoColor> = {
|
||||||
[ETagColor.INFO]: ETypoColor.COLOR_INFO_900,
|
[ETagColor.INFO]: ETypoColor.TAG_INFO_CONTRAST,
|
||||||
[ETagColor.SUCCESS]: ETypoColor.COLOR_SUCCESS_700,
|
[ETagColor.SUCCESS]: ETypoColor.TAG_SUCCESS_CONTRAST,
|
||||||
[ETagColor.ERROR]: ETypoColor.COLOR_SECONDARY_700,
|
[ETagColor.ERROR]: ETypoColor.TAG_ERROR_CONTRAST,
|
||||||
[ETagColor.WARNING]: ETypoColor.COLOR_WARNING_700,
|
[ETagColor.WARNING]: ETypoColor.TAG_WARNING_CONTRAST,
|
||||||
|
[ETagColor.NEUTRAL]: ETypoColor.TAG_NEUTRAL_CONTRAST,
|
||||||
};
|
};
|
||||||
|
|
||||||
const typoMap: Record<ETagVariant, ETypo> = {
|
const typoMap: Record<ETagVariant, ETypo> = {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg, 24px);
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Typography, { ETypo, ETypoColor } from "../../Typography";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ToastContent(props: IProps) {
|
||||||
|
const { icon, title, description } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
{icon}
|
||||||
|
<div className={classes["content"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.TOASTER_CONTRAST_TITLE}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TOASTER_CONTRAST_TEXT}>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
.root {
|
||||||
|
width: fit-content !important;
|
||||||
|
.wrapper {
|
||||||
|
width: 387px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: var(--spacing-2, 16px);
|
||||||
|
border-radius: var(--toaster-radius, 0px);
|
||||||
|
border: 1px solid var(--toaster-border, #e5eefa);
|
||||||
|
background: var(--toaster-background, #fff);
|
||||||
|
/* shadow/sm */
|
||||||
|
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.body {
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
src/front/Components/DesignSystem/Toaster/index.tsx
Normal file
111
src/front/Components/DesignSystem/Toaster/index.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { ArrowLeftStartOnRectangleIcon, CheckIcon, ExclamationTriangleIcon, InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
|
import React from "react";
|
||||||
|
import { toast, ToastContainer } from "react-toastify";
|
||||||
|
|
||||||
|
import BadgeIcon, { EBadgeColor } from "../BadgeIcon";
|
||||||
|
import IconButton from "../IconButton";
|
||||||
|
import Loader from "../Loader";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import ToastContent from "./ToastContent";
|
||||||
|
|
||||||
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
|
||||||
|
export default function Toaster() {
|
||||||
|
return (
|
||||||
|
<ToastContainer
|
||||||
|
className={classes["root"]}
|
||||||
|
toastClassName={classes["wrapper"]}
|
||||||
|
bodyClassName={classes["body"]}
|
||||||
|
progressClassName={classes["progress"]}
|
||||||
|
closeButton={<IconButton icon={<XMarkIcon />} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ToasterService {
|
||||||
|
private static instance: ToasterService;
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance() {
|
||||||
|
return (this.instance ??= new this());
|
||||||
|
}
|
||||||
|
|
||||||
|
public text({ title, description }: { title: string; description?: string }) {
|
||||||
|
toast.info(<ToastContent title={title} description={description} />, {
|
||||||
|
icon: false,
|
||||||
|
progressStyle: { backgroundColor: "var(--primary-default-base)" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public info({ title, description }: { title: string; description?: string }) {
|
||||||
|
toast.info(
|
||||||
|
<ToastContent
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
icon={<BadgeIcon icon={<InformationCircleIcon />} color={EBadgeColor.INFO} />}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
icon: false,
|
||||||
|
progressStyle: { backgroundColor: "var(--info-default-base)" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public success({ title, description }: { title: string; description?: string }) {
|
||||||
|
toast.info(
|
||||||
|
<ToastContent title={title} description={description} icon={<BadgeIcon icon={<CheckIcon />} color={EBadgeColor.SUCCESS} />} />,
|
||||||
|
{
|
||||||
|
icon: false,
|
||||||
|
progressStyle: { backgroundColor: "var(--success-default-base)" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error({ title, description }: { title: string; description?: string }) {
|
||||||
|
toast.info(
|
||||||
|
<ToastContent title={title} description={description} icon={<BadgeIcon icon={<XMarkIcon />} color={EBadgeColor.ERROR} />} />,
|
||||||
|
{
|
||||||
|
icon: false,
|
||||||
|
progressStyle: { backgroundColor: "var(--error-default-base)" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warning({ title, description }: { title: string; description?: string }) {
|
||||||
|
toast.info(
|
||||||
|
<ToastContent
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
icon={<BadgeIcon icon={<ExclamationTriangleIcon />} color={EBadgeColor.WARNING} />}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
icon: false,
|
||||||
|
progressStyle: { backgroundColor: "var(--warning-default-base)" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public loading({ title, description }: { title: string; description?: string }) {
|
||||||
|
toast.info(
|
||||||
|
<ToastContent title={title} description={description} icon={<BadgeIcon icon={<Loader />} color={EBadgeColor.INFO} />} />,
|
||||||
|
{
|
||||||
|
icon: false,
|
||||||
|
autoClose: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public loggedOut({ title, description }: { title: string; description?: string }) {
|
||||||
|
toast.info(
|
||||||
|
<ToastContent
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
icon={<BadgeIcon icon={<ArrowLeftStartOnRectangleIcon />} color={EBadgeColor.NEUTRAL} />}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
icon: false,
|
||||||
|
autoClose: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,6 @@ class ToastElementClass extends React.Component<IPropsClass, IState> {
|
|||||||
|
|
||||||
public override render(): JSX.Element {
|
public override render(): JSX.Element {
|
||||||
const toast = this.props.toast;
|
const toast = this.props.toast;
|
||||||
console.log(toast);
|
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
"--data-duration": `${toast.time}ms`,
|
"--data-duration": `${toast.time}ms`,
|
||||||
|
27
src/front/Components/DesignSystem/TooltipElement/index.tsx
Normal file
27
src/front/Components/DesignSystem/TooltipElement/index.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { styled, Tooltip, tooltipClasses, TooltipProps } from "@mui/material";
|
||||||
|
|
||||||
|
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||||
|
|
||||||
|
type IProps = TooltipProps;
|
||||||
|
|
||||||
|
export default function TooltipElement(props: IProps) {
|
||||||
|
const CustomTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||||
|
<Tooltip
|
||||||
|
{...props}
|
||||||
|
title={
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
{props.title}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
classes={{ popper: className }}
|
||||||
|
/>
|
||||||
|
))(({ theme }) => ({
|
||||||
|
[`& .${tooltipClasses.tooltip}`]: {
|
||||||
|
backgroundColor: "var(--tooltip-background, #FFF)",
|
||||||
|
boxShadow: theme.shadows[1],
|
||||||
|
borderRadius: "var(--tooltip-radius, 4px)",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return <CustomTooltip {...props} />;
|
||||||
|
}
|
@ -278,4 +278,8 @@
|
|||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
letter-spacing: -0.8px;
|
letter-spacing: -0.8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ type IProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
type?: "div" | "span";
|
type?: "div" | "span";
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
italic?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ETypo {
|
export enum ETypo {
|
||||||
@ -149,6 +150,8 @@ export enum ETypoColor {
|
|||||||
INPUT_ERROR = "--input-error",
|
INPUT_ERROR = "--input-error",
|
||||||
|
|
||||||
TEXT_ACCENT = "--text-accent",
|
TEXT_ACCENT = "--text-accent",
|
||||||
|
TEXT_PRIMARY = "--text-primary",
|
||||||
|
TEXT_SECONDARY = "--text-secondary",
|
||||||
|
|
||||||
CONTRAST_DEFAULT = "--contrast-default",
|
CONTRAST_DEFAULT = "--contrast-default",
|
||||||
CONTRAST_HOVERED = "--contrast-hovered",
|
CONTRAST_HOVERED = "--contrast-hovered",
|
||||||
@ -161,23 +164,41 @@ export enum ETypoColor {
|
|||||||
DROPDOWN_CONTRAST_ACTIVE = "--dropdown-contrast-active",
|
DROPDOWN_CONTRAST_ACTIVE = "--dropdown-contrast-active",
|
||||||
|
|
||||||
INPUT_CHIP_CONTRAST = "--input-chip-contrast",
|
INPUT_CHIP_CONTRAST = "--input-chip-contrast",
|
||||||
|
|
||||||
|
TAG_NEUTRAL_CONTRAST = "--tag-neutral-contrast",
|
||||||
|
TAG_INFO_CONTRAST = "--tag-info-contrast",
|
||||||
|
TAG_SUCCESS_CONTRAST = "--tag-success-contrast",
|
||||||
|
TAG_WARNING_CONTRAST = "--tag-warning-contrast",
|
||||||
|
TAG_ERROR_CONTRAST = "--tag-error-contrast",
|
||||||
|
|
||||||
|
TOASTER_CONTRAST_TITLE = "--toaster-contrast-title",
|
||||||
|
TOASTER_CONTRAST_TEXT = "--toaster-contrast-text",
|
||||||
|
|
||||||
|
TABLE_COLUMN_CONTRAST = "--table-column-contrast",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Typography(props: IProps) {
|
export default function Typography(props: IProps) {
|
||||||
const { typo, color, className, title, children, type = "div", onClick } = props;
|
const { typo, color, className, title, children, type = "div", onClick, italic } = props;
|
||||||
|
|
||||||
const style = color ? ({ "--data-color": `var(${color})` } as React.CSSProperties) : undefined;
|
const style = color ? ({ "--data-color": `var(${color})` } as React.CSSProperties) : undefined;
|
||||||
|
|
||||||
if (type === "span") {
|
if (type === "span") {
|
||||||
return (
|
return (
|
||||||
<span className={classNames(classes["root"], classes[typo], className)} style={style} title={title}>
|
<span
|
||||||
|
className={classNames(classes["root"], classes[typo], className, italic && classes["italic"])}
|
||||||
|
style={style}
|
||||||
|
title={title}>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(classes["root"], classes[typo], className)} style={style} title={title} onClick={onClick}>
|
<div
|
||||||
|
className={classNames(classes["root"], classes[typo], className, italic && classes["italic"])}
|
||||||
|
style={style}
|
||||||
|
title={title}
|
||||||
|
onClick={onClick}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@ import React from "react";
|
|||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
url?: string;
|
url?: string;
|
||||||
|
text?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IPropsClass = IProps & {
|
type IPropsClass = IProps & {
|
||||||
@ -26,7 +27,7 @@ class BackArrowClass extends React.Component<IPropsClass, IState> {
|
|||||||
styletype={EButtonstyletype.TEXT}
|
styletype={EButtonstyletype.TEXT}
|
||||||
size={EButtonSize.SM}
|
size={EButtonSize.SM}
|
||||||
onClick={this.handleClick}>
|
onClick={this.handleClick}>
|
||||||
Retour
|
{this.props.text ?? "Retour"}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
22
src/front/Components/Elements/ContactBox/classes.module.scss
Normal file
22
src/front/Components/Elements/ContactBox/classes.module.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
padding: var(--spacing-md, 16px);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md, 16px);
|
||||||
|
background: var(--primary-weak-higlight, #e5eefa);
|
||||||
|
min-width: 300px;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
48
src/front/Components/Elements/ContactBox/index.tsx
Normal file
48
src/front/Components/Elements/ContactBox/index.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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;
|
||||||
|
note: Note | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ContactBox(props: IProps) {
|
||||||
|
const { contact, note } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<div className={classes["header"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_PRIMARY_500}>
|
||||||
|
{contact?.first_name} {contact?.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}>
|
||||||
|
{contact?.cell_phone_number ?? contact?.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}>
|
||||||
|
{contact?.email ?? "_"}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||||
|
Note client
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||||
|
{note?.content ?? "-"}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
}
|
39
src/front/Components/Elements/HelpBox/index.tsx
Normal file
39
src/front/Components/Elements/HelpBox/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export type IProps = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
button: { text: string; link?: string; onClick?: () => void };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function HelpBox(props: IProps) {
|
||||||
|
const { title, description, button } = props;
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
{button.link ? (
|
||||||
|
<Link href={button.link}>
|
||||||
|
<Button
|
||||||
|
variant={EButtonVariant.SECONDARY}
|
||||||
|
styletype={EButtonstyletype.TEXT}
|
||||||
|
size={EButtonSize.MD}
|
||||||
|
onClick={button.onClick}>
|
||||||
|
{button.text}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Button variant={EButtonVariant.SECONDARY} styletype={EButtonstyletype.TEXT} size={EButtonSize.MD} onClick={button.onClick}>
|
||||||
|
{button.text}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -80,7 +80,6 @@ export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
|
|||||||
newTabs.splice(index, 1);
|
newTabs.splice(index, 1);
|
||||||
newTabs.unshift(tabs.current[index]!);
|
newTabs.unshift(tabs.current[index]!);
|
||||||
tabs.current = newTabs;
|
tabs.current = newTabs;
|
||||||
console.log("Updated values ; ", tabs.current);
|
|
||||||
handleSelect(value);
|
handleSelect(value);
|
||||||
},
|
},
|
||||||
[handleSelect],
|
[handleSelect],
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||||
|
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||||
|
import { OfficeFolder } from "le-coffre-resources/dist/Customer";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||||
|
|
||||||
|
type IProps = IPropsDashboardWithList & {};
|
||||||
|
|
||||||
|
export default function DefaultCustomerDashboard(props: IProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const { folderUid } = router.query;
|
||||||
|
const [folders, setFolders] = useState<OfficeFolder[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||||
|
if (!jwt) return;
|
||||||
|
|
||||||
|
Folders.getInstance()
|
||||||
|
.get({
|
||||||
|
q: {
|
||||||
|
where: {
|
||||||
|
customers: {
|
||||||
|
some: {
|
||||||
|
contact: {
|
||||||
|
email: jwt.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: [
|
||||||
|
{
|
||||||
|
created_at: "desc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
include: {
|
||||||
|
customers: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((folders) => setFolders(folders));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSelectedBlock = (block: IBlock) => {
|
||||||
|
const folder = folders.find((folder) => folder.uid === block.id);
|
||||||
|
if (!folder) return;
|
||||||
|
router.push(
|
||||||
|
Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.ClientDashboard.props.path.replace("[folderUid]", folder.uid ?? ""),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return <DefaultDashboardWithList {...props} onSelectedBlock={onSelectedBlock} blocks={getBlocks(folders)} headerConnected={false}/>;
|
||||||
|
|
||||||
|
function getBlocks(folders: OfficeFolder[]): IBlock[] {
|
||||||
|
return folders.map((folder) => {
|
||||||
|
return {
|
||||||
|
id: folder.uid!,
|
||||||
|
primaryText: folder.name!,
|
||||||
|
secondaryText: folder.folder_number!,
|
||||||
|
isActive: folderUid === folder.uid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,10 @@
|
|||||||
|
|
||||||
.right-side {
|
.right-side {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
|
||||||
|
.right-side-content {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import Toaster from "../DesignSystem/Toaster";
|
||||||
|
|
||||||
type DefaultLayoutProps = { children: ReactNode };
|
type DefaultLayoutProps = { children: ReactNode };
|
||||||
import { ToastContainer } from "react-toastify";
|
|
||||||
import "react-toastify/dist/ReactToastify.css";
|
|
||||||
export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -12,7 +13,7 @@ export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
|||||||
</Head>
|
</Head>
|
||||||
<main>
|
<main>
|
||||||
{children}
|
{children}
|
||||||
<ToastContainer />
|
<Toaster />
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import DragAndDrop, { IDocumentFileWithUid } from "@Front/Components/DesignSystem/DragAndDrop";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import { Document } from "le-coffre-resources/dist/Customer";
|
||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
|
||||||
|
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
document: Document;
|
||||||
|
onChange: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DepositDocumentComponent(props: IProps) {
|
||||||
|
const { document, onChange } = props;
|
||||||
|
|
||||||
|
const defaultFiles: IDocumentFileWithUid[] = useMemo(() => {
|
||||||
|
const filesNotArchived = document.files?.filter((file) => !file.archived_at) ?? [];
|
||||||
|
return filesNotArchived.map((file) => ({
|
||||||
|
id: file.uid!,
|
||||||
|
file: new File([""], file.file_name!, { type: file.mimetype }),
|
||||||
|
uid: file.uid!,
|
||||||
|
}));
|
||||||
|
}, [document.files]);
|
||||||
|
|
||||||
|
const addFile = useCallback(
|
||||||
|
(file: File) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file, file.name);
|
||||||
|
const query = JSON.stringify({ document: { uid: document.uid } });
|
||||||
|
formData.append("q", query);
|
||||||
|
return Files.getInstance()
|
||||||
|
.post(formData)
|
||||||
|
.then(onChange)
|
||||||
|
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier uploadé avec succès!" }))
|
||||||
|
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
|
||||||
|
},
|
||||||
|
[document.uid, onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteFile = useCallback(
|
||||||
|
(filedUid: string) => {
|
||||||
|
return Files.getInstance()
|
||||||
|
.delete(filedUid)
|
||||||
|
.then(onChange)
|
||||||
|
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier supprimé avec succès!" }))
|
||||||
|
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<div className={classes["title"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
{document.document_type?.name ?? "_"}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Demandé le: {document.created_at ? new Date(document.created_at).toLocaleDateString() : "_"}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DragAndDrop
|
||||||
|
title={"Drag and drop ou"}
|
||||||
|
description={document.document_type?.public_description ?? undefined}
|
||||||
|
defaultFiles={defaultFiles}
|
||||||
|
onAddFile={addFile}
|
||||||
|
onDelete={deleteFile}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
padding: var(--spacing-3) var(--spacing-15);
|
||||||
|
max-width: 1156px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
.table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-m) {
|
||||||
|
padding: var(--spacing-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
padding: var(--spacing-2);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||||
|
import FilesNotary from "@Front/Api/LeCoffreApi/Notary/FilesNotary/Files";
|
||||||
|
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
|
import IconButton from "@Front/Components/DesignSystem/IconButton";
|
||||||
|
import Table from "@Front/Components/DesignSystem/Table";
|
||||||
|
import { IHead, IRowProps } from "@Front/Components/DesignSystem/Table/MuiTable";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||||
|
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||||
|
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { saveAs } from "file-saver";
|
||||||
|
import JSZip from "jszip";
|
||||||
|
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
const header: readonly IHead[] = [
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
title: "Nom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "sentAt",
|
||||||
|
title: "Envoyé le",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "actions",
|
||||||
|
title: "Action",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function ReceivedDocuments() {
|
||||||
|
const router = useRouter();
|
||||||
|
let { folderUid } = router.query;
|
||||||
|
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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]);
|
||||||
|
|
||||||
|
const onDownload = useCallback((doc: DocumentNotary) => {
|
||||||
|
const file = doc.files?.[0];
|
||||||
|
if (!file || !file?.uid) return;
|
||||||
|
|
||||||
|
return FilesNotary.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 onDownloadAll = useCallback(async () => {
|
||||||
|
if (documentsNotary.length === 0) return;
|
||||||
|
|
||||||
|
const zip = new JSZip();
|
||||||
|
const folder = zip.folder("documents") || zip;
|
||||||
|
|
||||||
|
const downloadPromises = documentsNotary.map(async (doc) => {
|
||||||
|
const file = doc.files?.[0];
|
||||||
|
if (file && file.uid) {
|
||||||
|
const blob = await FilesNotary.getInstance().download(file.uid);
|
||||||
|
folder.file(file.file_name ?? "file", blob);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(downloadPromises);
|
||||||
|
|
||||||
|
zip.generateAsync({ type: "blob" })
|
||||||
|
.then((blob: any) => {
|
||||||
|
saveAs(blob, "documents.zip");
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.error("Error generating ZIP file: ", error);
|
||||||
|
});
|
||||||
|
}, [documentsNotary]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaultTemplate title={"Documents reçus"} isPadding={false}>
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<BackArrow
|
||||||
|
text="Retour aux dossiers"
|
||||||
|
url={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.ClientDashboard.props.path.replace("[folderUid]", folderUid as string)}
|
||||||
|
/>
|
||||||
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
Un document vous a été envoyé
|
||||||
|
</Typography>
|
||||||
|
<Table className={classes["table"]} header={header} rows={buildRows(documentsNotary, onDownload)} />
|
||||||
|
<Button
|
||||||
|
variant={EButtonVariant.PRIMARY}
|
||||||
|
size={EButtonSize.LG}
|
||||||
|
styletype={EButtonstyletype.CONTAINED}
|
||||||
|
leftIcon={<ArrowDownTrayIcon />}
|
||||||
|
onClick={onDownloadAll}>
|
||||||
|
Tout télécharger
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DefaultTemplate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRows(documentsNotary: DocumentNotary[], onDownloadFileNotary: (doc: DocumentNotary) => void): IRowProps[] {
|
||||||
|
return documentsNotary.map((documentNotary) => ({
|
||||||
|
key: documentNotary.uid ?? "",
|
||||||
|
name: documentNotary.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
|
||||||
|
sentAt: new Date(documentNotary.created_at!).toLocaleDateString(),
|
||||||
|
actions: <IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />,
|
||||||
|
}));
|
||||||
|
}
|
@ -1,120 +1,39 @@
|
|||||||
@import "@Themes/constants.scss";
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
.header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 64px;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
@media (max-width: $screen-m) {
|
.title-container {
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
|
|
||||||
.text {
|
|
||||||
margin: 32px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $screen-s) {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin: 32px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-number {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.office-name {
|
|
||||||
margin-top: 8px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
margin: 64px 0 32px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: var(--spacing-sm, 8px);
|
||||||
}
|
|
||||||
|
|
||||||
.contact-text {
|
.office-container {
|
||||||
text-align: right;
|
display: flex;
|
||||||
line-height: 15px;
|
align-items: center;
|
||||||
}
|
gap: var(--spacing-md, 16px);
|
||||||
|
|
||||||
.contact-button {
|
|
||||||
margin-top: 4%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
width: 20px;
|
|
||||||
height: 50px; /* Adjust the height as needed */
|
|
||||||
background-color: gray;
|
|
||||||
margin: 0 20px; /* Adjust the margin as needed */
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-box {
|
|
||||||
border: 1px solid #e0e0e0; /* Light grey border */
|
|
||||||
margin-top: 25px;
|
|
||||||
padding: 10px;
|
|
||||||
width: 100%;
|
|
||||||
height: 100px; /* Adjust height as needed */
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-container {
|
|
||||||
background-color: var(--color-neutral-50);
|
|
||||||
padding: 64px;
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr;
|
gap: var(--spacing-lg, 24px);
|
||||||
gap: 32px;
|
align-items: flex-start;
|
||||||
margin-bottom: 64px;
|
|
||||||
|
|
||||||
@media (max-width: $screen-s) {
|
.notary {
|
||||||
grid-template-columns: 1fr;
|
display: flex;
|
||||||
}
|
width: 300px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-lg, 24px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-to-replace {
|
.documents {
|
||||||
min-width: 124px;
|
display: flex;
|
||||||
height: 98px;
|
flex-direction: column;
|
||||||
background-color: white;
|
gap: var(--spacing-xl, 32px);
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin: 32px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $screen-s) {
|
|
||||||
.button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
.text {
|
|
||||||
margin: 24px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.component-to-replace {
|
|
||||||
background-color: var(--color-neutral-50);
|
|
||||||
height: 98px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/Documents/Documents";
|
import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/Documents/Documents";
|
||||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
|
||||||
import DepositDocument from "@Front/Components/DesignSystem/DepositDocument";
|
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
|
||||||
import Customer, { Document, DocumentType, Note, OfficeFolder } from "le-coffre-resources/dist/Customer";
|
import Customer, { Document } from "le-coffre-resources/dist/Customer";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { type OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
|
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||||
import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument";
|
|
||||||
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||||
import OfficeRib from "@Front/Api/LeCoffreApi/Customer/OfficeRib/OfficeRib";
|
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 DepositDocumentComponent from "./DepositDocumentComponent";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
|
||||||
type IProps = {};
|
type IProps = {};
|
||||||
|
|
||||||
export default function ClientDashboard(props: IProps) {
|
export default function ClientDashboard(props: IProps) {
|
||||||
@ -22,12 +30,11 @@ export default function ClientDashboard(props: IProps) {
|
|||||||
const [documents, setDocuments] = useState<Document[] | null>(null);
|
const [documents, setDocuments] = useState<Document[] | null>(null);
|
||||||
|
|
||||||
const [customer, setCustomer] = useState<Customer | null>(null);
|
const [customer, setCustomer] = useState<Customer | null>(null);
|
||||||
const [contact, setContact] = useState<Customer["contact"] | null>(null);
|
const [folder, setFolder] = useState<OfficeFolderNotary | null>(null);
|
||||||
const [folder, setFolder] = useState<OfficeFolder | null>(null);
|
|
||||||
const [note, setNote] = useState<Note | null>(null);
|
|
||||||
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const getDocuments = useCallback(async () => {
|
const [ribUrl, setRibUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const fetchFolderAndCustomer = useCallback(async () => {
|
||||||
let jwt: ICustomerJwtPayload | undefined;
|
let jwt: ICustomerJwtPayload | undefined;
|
||||||
if (typeof document !== "undefined") {
|
if (typeof document !== "undefined") {
|
||||||
jwt = JwtService.getInstance().decodeCustomerJwt();
|
jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||||
@ -50,25 +57,20 @@ export default function ClientDashboard(props: IProps) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
//Loop through the folder stakeholders, if there is at least one stakeholder that role is "Collaborateur" set contact to this stakeholders.contact, else, take the first stakeholders of the list
|
|
||||||
const contact = folder.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact;
|
|
||||||
setContact(contact ?? folder.stakeholders![0]!.contact);
|
|
||||||
|
|
||||||
const actualCustomer = folder?.customers?.find((customer) => customer.contact?.email === jwt?.email);
|
const customer = folder?.customers?.find((customer) => customer.contact?.email === jwt?.email);
|
||||||
if (!actualCustomer) throw new Error("Customer not found");
|
if (!customer) throw new Error("Customer not found");
|
||||||
|
|
||||||
let note = folder.notes?.find((note) => note.customer?.uid === actualCustomer.uid);
|
setFolder(folder);
|
||||||
// if (!note) throw new Error("Note not found");
|
setCustomer(customer);
|
||||||
if (!note) {
|
|
||||||
note = {
|
|
||||||
content: "Aucune note",
|
|
||||||
created_at: new Date(),
|
|
||||||
updated_at: new Date(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return { folder, customer };
|
||||||
|
}, [folderUid]);
|
||||||
|
|
||||||
|
const fetchDocuments = useCallback(
|
||||||
|
(customerUid: string | undefined) => {
|
||||||
const query: IGetDocumentsparams = {
|
const query: IGetDocumentsparams = {
|
||||||
where: { depositor: { uid: actualCustomer.uid }, folder_uid: folderUid as string },
|
where: { depositor: { uid: customerUid }, folder_uid: folderUid as string },
|
||||||
include: {
|
include: {
|
||||||
files: true,
|
files: true,
|
||||||
document_history: true,
|
document_history: true,
|
||||||
@ -86,30 +88,42 @@ export default function ClientDashboard(props: IProps) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const documentList = await Documents.getInstance().get(query);
|
return Documents.getInstance()
|
||||||
|
.get(query)
|
||||||
|
.then((documents) => setDocuments(documents));
|
||||||
|
},
|
||||||
|
[folderUid],
|
||||||
|
);
|
||||||
|
|
||||||
//const folder = await Folders.getInstance().getByUid(folderUid as string, { q: { office: true, customers: true } });
|
useEffect(() => {
|
||||||
|
fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid));
|
||||||
|
}, [fetchDocuments, fetchFolderAndCustomer]);
|
||||||
|
|
||||||
setFolder(folder);
|
const notaryContact = useMemo(
|
||||||
setDocuments(documentList);
|
() =>
|
||||||
setCustomer(actualCustomer);
|
folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact ??
|
||||||
setNote(note);
|
folder?.stakeholders![0]!.contact,
|
||||||
}, [folderUid]);
|
[folder],
|
||||||
|
);
|
||||||
|
|
||||||
const onCloseModalAddDocument = useCallback(() => {
|
const note = useMemo(
|
||||||
setIsAddDocumentModalVisible(false);
|
() =>
|
||||||
getDocuments();
|
folder?.notes?.find((note) => note.customer?.uid === customer?.uid) ?? {
|
||||||
}, [getDocuments]);
|
content: "Aucune note",
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date(),
|
||||||
|
},
|
||||||
|
[customer?.uid, folder?.notes],
|
||||||
|
);
|
||||||
|
|
||||||
const onOpenModalAddDocument = useCallback(() => {
|
useEffect(() => {
|
||||||
setIsAddDocumentModalVisible(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const downloadFile = useCallback(async () => {
|
|
||||||
if (!folder?.office?.uid) return;
|
if (!folder?.office?.uid) return;
|
||||||
const blob = await OfficeRib.getInstance().getRibStream(folder.office.uid);
|
OfficeRib.getInstance()
|
||||||
const ribUrl = URL.createObjectURL(blob);
|
.getRibStream(folder.office.uid)
|
||||||
|
.then((blob) => setRibUrl(URL.createObjectURL(blob)));
|
||||||
|
}, [folder]);
|
||||||
|
|
||||||
|
const downloadRib = useCallback(async () => {
|
||||||
if (!ribUrl) return;
|
if (!ribUrl) return;
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.style.display = "none";
|
a.style.display = "none";
|
||||||
@ -117,122 +131,73 @@ export default function ClientDashboard(props: IProps) {
|
|||||||
a.download = "";
|
a.download = "";
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
}, [folder]);
|
}, [ribUrl]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getDocuments();
|
|
||||||
}, [folderUid, getDocuments]);
|
|
||||||
|
|
||||||
const renderHeader = useCallback(() => {
|
|
||||||
return (
|
return (
|
||||||
<div className={classes["header"]}>
|
<DefaultCustomerDashboard>
|
||||||
<div className={classes["text-container"]}>
|
<div className={classes["root"]}>
|
||||||
{/* TODO Get name from userStore */}
|
|
||||||
<div className={classes["title-container"]}>
|
<div className={classes["title-container"]}>
|
||||||
<Typography typo={ETypo.DISPLAY_LARGE} className={classes["title"]}>
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} className={classes["folder-number"]} color={ETypoColor.COLOR_NEUTRAL_500}>
|
|
||||||
Dossier {folder?.folder_number} - {folder?.name}
|
Dossier {folder?.folder_number} - {folder?.name}
|
||||||
</Typography>
|
</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"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Office
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} className={classes["office-name"]} color={ETypoColor.COLOR_NEUTRAL_500}>
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||||
{folder?.office?.name}
|
{folder?.office?.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography typo={ETypo.TITLE_H3} className={classes["subtitle"]}>
|
|
||||||
Documents à envoyer
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
Votre notaire est dans l'attente de documents pour valider votre dossier. Voici la liste des documents.
|
|
||||||
<br /> Veuillez glisser / déposer chaque document dans la zone prévue à cet effet ou cliquez sur la zone puis
|
|
||||||
sélectionnez le document correspondant. <br /> En déposant un document, celui-ci est automatiquement enregistré et
|
|
||||||
transmis à votre notaire.
|
|
||||||
</Typography>
|
|
||||||
<div className={classes["note-box"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
|
|
||||||
{note?.content}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={classes["content"]}>
|
||||||
<div className={classes["contact"]}>
|
<div className={classes["notary"]}>
|
||||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} className={classes["contact-text"]} color={ETypoColor.COLOR_NEUTRAL_500}>
|
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.TABLE_COLUMN_CONTRAST}>
|
||||||
<p>
|
Votre Notaire
|
||||||
{contact?.first_name} {contact?.last_name}
|
|
||||||
</p>
|
|
||||||
<p>{contact?.phone_number ?? contact?.cell_phone_number}</p>
|
|
||||||
<p>{contact?.email}</p>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className="separator"></div>
|
{notaryContact && <ContactBox contact={notaryContact} note={note} />}
|
||||||
{folder?.office?.rib_name && (
|
{ribUrl && (
|
||||||
//Div to avoid the button to be on the same line as the text
|
<Button
|
||||||
<Button className={classes["contact-button"]} onClick={downloadFile}>
|
fullwidth
|
||||||
Télécharger le RIB de votre notaire
|
onClick={downloadRib}
|
||||||
|
variant={EButtonVariant.PRIMARY}
|
||||||
|
size={EButtonSize.LG}
|
||||||
|
styletype={EButtonstyletype.CONTAINED}
|
||||||
|
rightIcon={<ArrowDownTrayIcon />}>
|
||||||
|
Télécharger le RIB
|
||||||
</Button>
|
</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>
|
||||||
</div>
|
<div className={classes["documents"]}>
|
||||||
);
|
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.TABLE_COLUMN_CONTRAST}>
|
||||||
}, [
|
Documents à envoyer
|
||||||
contact?.cell_phone_number,
|
</Typography>
|
||||||
contact?.email,
|
|
||||||
contact?.first_name,
|
|
||||||
contact?.last_name,
|
|
||||||
contact?.phone_number,
|
|
||||||
customer?.contact?.first_name,
|
|
||||||
customer?.contact?.last_name,
|
|
||||||
downloadFile,
|
|
||||||
folder?.folder_number,
|
|
||||||
folder?.name,
|
|
||||||
folder?.office?.name,
|
|
||||||
folder?.office?.rib_name,
|
|
||||||
note?.content,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const renderBox = useCallback(() => {
|
|
||||||
return (
|
|
||||||
<DepositOtherDocument
|
|
||||||
folder_uid={folderUid!}
|
|
||||||
customer_uid={customer!.uid!}
|
|
||||||
open={isAddDocumentModalVisible}
|
|
||||||
onClose={onCloseModalAddDocument}
|
|
||||||
document={Document.hydrate<Document>({
|
|
||||||
document_type: DocumentType.hydrate<DocumentType>({
|
|
||||||
name: "Autres documents",
|
|
||||||
}),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DefaultTemplate title={"Mon compte"} isPadding={false} hasHeaderLinks={false}>
|
|
||||||
<div className={classes["root"]}>
|
|
||||||
{renderHeader()}
|
|
||||||
<div className={classes["sub-container"]}>
|
|
||||||
<div className={classes["content"]}>
|
|
||||||
{documents?.map((document) => (
|
{documents?.map((document) => (
|
||||||
<DepositDocument document={document} key={document.uid} defaultFiles={document.files ?? []} />
|
<DepositDocumentComponent
|
||||||
|
key={document.uid}
|
||||||
|
document={document}
|
||||||
|
onChange={() => fetchDocuments(customer?.uid)}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Typography typo={ETypo.TITLE_H3}>Documents supplémentaires (facultatif)</Typography>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
Vous souhaitez envoyer d'autres documents à votre notaire ?
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
variant={EButtonVariant.PRIMARY}
|
|
||||||
styletype={EButtonstyletype.OUTLINED}
|
|
||||||
className={classes["button"]}
|
|
||||||
onClick={onOpenModalAddDocument}>
|
|
||||||
Ajouter d'autres documents
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isAddDocumentModalVisible && renderBox()}
|
</DefaultCustomerDashboard>
|
||||||
</DefaultTemplate>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
//import Customers from "@Front/Api/LeCoffreApi/Customer/Customers/Customers";
|
|
||||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
|
||||||
import DepositDocument from "@Front/Components/DesignSystem/DepositDocument";
|
|
||||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
|
||||||
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
|
|
||||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
|
||||||
import Base from "@Front/Components/Layouts/Base";
|
|
||||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
|
||||||
import Customer, { Document, DocumentType } from "le-coffre-resources/dist/Customer";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
|
||||||
|
|
||||||
type IProps = {};
|
|
||||||
type IState = {
|
|
||||||
isAddDocumentModalVisible: boolean;
|
|
||||||
documents: Document[];
|
|
||||||
mockedCustomer: Customer | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class ClientDashboard extends Base<IProps, IState> {
|
|
||||||
public constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
isAddDocumentModalVisible: false,
|
|
||||||
documents: [],
|
|
||||||
mockedCustomer: null,
|
|
||||||
};
|
|
||||||
this.onCloseModalAddDocument = this.onCloseModalAddDocument.bind(this);
|
|
||||||
this.onOpenModalAddDocument = this.onOpenModalAddDocument.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override render(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<DefaultTemplate title={"Mon compte"} isPadding={false} hasHeaderLinks={false}>
|
|
||||||
<div className={classes["root"]}>
|
|
||||||
{this.renderHeader()}
|
|
||||||
<div className={classes["sub-container"]}>
|
|
||||||
<div className={classes["content"]}>
|
|
||||||
{this.state.documents?.map((document) => (
|
|
||||||
<DepositDocument document={document} key={document.uid} defaultFiles={document.files ?? []} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Typography typo={ETypo.TITLE_H3}>Documents supplémentaires (facultatif)</Typography>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
Vous souhaitez envoyer d'autres documents à votre notaire ?
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
variant={EButtonVariant.PRIMARY}
|
|
||||||
styletype={EButtonstyletype.OUTLINED}
|
|
||||||
className={classes["button"]}
|
|
||||||
onClick={this.onOpenModalAddDocument}>
|
|
||||||
Ajouter d'autres documents
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Confirm
|
|
||||||
isOpen={this.state.isAddDocumentModalVisible}
|
|
||||||
onClose={this.onCloseModalAddDocument}
|
|
||||||
onAccept={this.onOpenModalAddDocument}
|
|
||||||
closeBtn
|
|
||||||
header={"Ajouter un document"}
|
|
||||||
cancelText={"Annuler"}
|
|
||||||
confirmText={"Déposer le document"}>
|
|
||||||
<div className={classes["modal-content"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
Vous souhaitez envoyer un autre document à votre notaire ?
|
|
||||||
</Typography>
|
|
||||||
<TextField placeholder="Nom du document" />
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
Glissez / Déposez votre document dans la zone prévue à cet effet ou cliquez sur la zone puis sélectionnez le
|
|
||||||
document correspondant.
|
|
||||||
</Typography>
|
|
||||||
<DepositDocument
|
|
||||||
document={Document.hydrate<Document>({
|
|
||||||
document_type: DocumentType.hydrate<DocumentType>({
|
|
||||||
name: "Autres documents",
|
|
||||||
}),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Confirm>
|
|
||||||
</div>
|
|
||||||
</DefaultTemplate>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderHeader(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className={classes["header"]}>
|
|
||||||
<div className={classes["text-container"]}>
|
|
||||||
{/* TODO Get name from userStore */}
|
|
||||||
<Typography typo={ETypo.DISPLAY_LARGE} className={classes["title"]}>
|
|
||||||
Bonjour {this.state.mockedCustomer?.contact?.first_name.concat(" ", this.state.mockedCustomer?.contact?.last_name)}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography typo={ETypo.TITLE_H3} className={classes["subtitle"]}>
|
|
||||||
Documents à envoyer
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
|
||||||
Votre notaire est dans l'attente de documents pour valider votre dossier. Voici la liste des documents.Veuillez
|
|
||||||
glisser / déposez chaque document dans la zone prévue à cet effet ou cliquez sur la zone puis sélectionnez le
|
|
||||||
document correspondant. Si un des documents demandés ne vous concernent pas, veuillez contacter votre notaire à
|
|
||||||
l'aide du bouton ci-dessus.
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// public override async componentDidMount() {
|
|
||||||
// // TODO Get documents of the current customer according to userStore
|
|
||||||
// // REMOVE this mock
|
|
||||||
|
|
||||||
// const jwt = JwtService.getInstance().decodeJwt();
|
|
||||||
// const mockedCustomers = await Customers.getInstance().get({
|
|
||||||
// where: { contact: { email: jwt?.email } },
|
|
||||||
// });
|
|
||||||
// const mockedCustomer: Customer = mockedCustomers[0]!;
|
|
||||||
|
|
||||||
// const query: IGetDocumentsparams = {
|
|
||||||
// where: { depositor: { uid: mockedCustomer.uid } },
|
|
||||||
// include: {
|
|
||||||
// files: true,
|
|
||||||
// document_history: true,
|
|
||||||
// document_type: true,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// const documents: Document[] = await Documents.getInstance().get(query);
|
|
||||||
// this.setState({ documents, mockedCustomer });
|
|
||||||
// }
|
|
||||||
|
|
||||||
private onCloseModalAddDocument() {
|
|
||||||
this.setState({ isAddDocumentModalVisible: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
private onOpenModalAddDocument() {
|
|
||||||
this.setState({ isAddDocumentModalVisible: true });
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,15 +3,16 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
.components {
|
.components {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
|
||||||
.inputs {
|
.inputs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
|
|
||||||
.rows {
|
.rows {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -2,7 +2,9 @@ import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
|
|||||||
import Autocomplete from "@Front/Components/DesignSystem/Autocomplete";
|
import Autocomplete from "@Front/Components/DesignSystem/Autocomplete";
|
||||||
import AutocompleteMultiSelect from "@Front/Components/DesignSystem/AutocompleteMultiSelect";
|
import AutocompleteMultiSelect from "@Front/Components/DesignSystem/AutocompleteMultiSelect";
|
||||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
|
import CheckboxesInputElement from "@Front/Components/DesignSystem/CheckBox";
|
||||||
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
||||||
|
import DragAndDrop from "@Front/Components/DesignSystem/DragAndDrop";
|
||||||
import Dropdown from "@Front/Components/DesignSystem/Dropdown";
|
import Dropdown from "@Front/Components/DesignSystem/Dropdown";
|
||||||
import Footer from "@Front/Components/DesignSystem/Footer";
|
import Footer from "@Front/Components/DesignSystem/Footer";
|
||||||
import Form from "@Front/Components/DesignSystem/Form";
|
import Form from "@Front/Components/DesignSystem/Form";
|
||||||
@ -12,10 +14,13 @@ import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/I
|
|||||||
import Menu from "@Front/Components/DesignSystem/Menu";
|
import Menu from "@Front/Components/DesignSystem/Menu";
|
||||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||||
import Newsletter from "@Front/Components/DesignSystem/Newsletter";
|
import Newsletter from "@Front/Components/DesignSystem/Newsletter";
|
||||||
|
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
||||||
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
|
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
|
||||||
import DropdownNavigation from "@Front/Components/DesignSystem/SearchBlockList/DropdownNavigation";
|
import DropdownNavigation from "@Front/Components/DesignSystem/SearchBlockList/DropdownNavigation";
|
||||||
|
import Separator, { ESeperatorColor, ESeperatorDirection } from "@Front/Components/DesignSystem/Separator";
|
||||||
import Table from "@Front/Components/DesignSystem/Table";
|
import Table from "@Front/Components/DesignSystem/Table";
|
||||||
import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag";
|
import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag";
|
||||||
|
import Toggle, { EToggleSize } from "@Front/Components/DesignSystem/Toggle";
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
import NumberPicker from "@Front/Components/Elements/NumberPicker";
|
import NumberPicker from "@Front/Components/Elements/NumberPicker";
|
||||||
import Tabs from "@Front/Components/Elements/Tabs";
|
import Tabs from "@Front/Components/Elements/Tabs";
|
||||||
@ -27,15 +32,15 @@ import {
|
|||||||
ArrowLongRightIcon,
|
ArrowLongRightIcon,
|
||||||
EllipsisHorizontalIcon,
|
EllipsisHorizontalIcon,
|
||||||
PencilSquareIcon,
|
PencilSquareIcon,
|
||||||
|
QuestionMarkCircleIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import CheckboxesInputElement from "@Front/Components/DesignSystem/CheckBox";
|
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||||
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
import TooltipElement from "@Front/Components/DesignSystem/TooltipElement";
|
||||||
import Toggle, { EToggleSize } from "@Front/Components/DesignSystem/Toggle";
|
|
||||||
|
|
||||||
export default function DesignSystem() {
|
export default function DesignSystem() {
|
||||||
const { isOpen, open, close } = useOpenable();
|
const { isOpen, open, close } = useOpenable();
|
||||||
@ -83,10 +88,78 @@ export default function DesignSystem() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultTemplate title={"DesignSystem"}>
|
<DefaultTemplate title={"DesignSystem"}>
|
||||||
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
|
|
||||||
<Newsletter isOpen={false} />
|
<Newsletter isOpen={false} />
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
<div />
|
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
|
||||||
|
<div className={classes["components"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Tooltip</Typography>
|
||||||
|
<div className={classes["rows"]}>
|
||||||
|
<TooltipElement title="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec">
|
||||||
|
<QuestionMarkCircleIcon width={24} />
|
||||||
|
</TooltipElement>
|
||||||
|
<TooltipElement title="Lorem ipsum dolor">
|
||||||
|
<span style={{ cursor: "help" }}>
|
||||||
|
<Typography typo={ETypo.TEXT_XS_LIGHT}>Work for any children</Typography>
|
||||||
|
</span>
|
||||||
|
</TooltipElement>
|
||||||
|
</div>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Toast</Typography>
|
||||||
|
<div className={classes["rows"]}>
|
||||||
|
<Button
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={() => ToasterService.getInstance().text({ title: "Title toaster", description: "Description" })}>
|
||||||
|
Send Toast
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={() => ToasterService.getInstance().info({ title: "Title toaster", description: "Description" })}>
|
||||||
|
Send Toast Info
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={() => ToasterService.getInstance().success({ title: "Title toaster", description: "Description" })}>
|
||||||
|
Send Toast Success
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={classes["rows"]}>
|
||||||
|
<Button
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={() => ToasterService.getInstance().warning({ title: "Title toaster", description: "Description" })}>
|
||||||
|
Send Toast Warning
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={() => ToasterService.getInstance().error({ title: "Title toaster", description: "Description" })}>
|
||||||
|
Send Toast Error
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={() => ToasterService.getInstance().loading({ title: "Title toaster", description: "Description" })}>
|
||||||
|
Send Toast Loading
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={() => ToasterService.getInstance().loggedOut({ title: "Title toaster", description: "Description" })}>
|
||||||
|
Logged Out
|
||||||
|
</Button>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Drag and Drop</Typography>
|
||||||
|
<DragAndDrop title="Upload de document" description="Description" />
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Separators</Typography>
|
||||||
|
<div className={classes["rows"]}>
|
||||||
|
<Separator color={ESeperatorColor.DEFAULT} direction={ESeperatorDirection.HORIZONTAL} />
|
||||||
|
<Separator color={ESeperatorColor.LIGHT} direction={ESeperatorDirection.HORIZONTAL} />
|
||||||
|
<Separator color={ESeperatorColor.STRONG} direction={ESeperatorDirection.HORIZONTAL} />
|
||||||
|
<Separator color={ESeperatorColor.CONTRAST} direction={ESeperatorDirection.HORIZONTAL} />
|
||||||
|
</div>
|
||||||
|
<div className={classes["rows"]} style={{ height: 70, justifyContent: "space-around" }}>
|
||||||
|
<Separator color={ESeperatorColor.DEFAULT} direction={ESeperatorDirection.VERTICAL} />
|
||||||
|
<Separator color={ESeperatorColor.LIGHT} direction={ESeperatorDirection.VERTICAL} />
|
||||||
|
<Separator color={ESeperatorColor.STRONG} direction={ESeperatorDirection.VERTICAL} />
|
||||||
|
<Separator color={ESeperatorColor.CONTRAST} direction={ESeperatorDirection.VERTICAL} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Checkboxes</Typography>
|
||||||
<div className={classes["rows"]}>
|
<div className={classes["rows"]}>
|
||||||
<CheckboxesInputElement
|
<CheckboxesInputElement
|
||||||
option={{
|
option={{
|
||||||
@ -125,7 +198,7 @@ export default function DesignSystem() {
|
|||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Radio boxes</Typography>
|
||||||
<div className={classes["rows"]}>
|
<div className={classes["rows"]}>
|
||||||
<RadioBox name="document" value={"new client"} description="Test" label="Créer un document" toolTip="test" />
|
<RadioBox name="document" value={"new client"} description="Test" label="Créer un document" toolTip="test" />
|
||||||
<RadioBox
|
<RadioBox
|
||||||
@ -154,7 +227,7 @@ export default function DesignSystem() {
|
|||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes["components"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Toggle</Typography>
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Toggle</Typography>
|
||||||
<div className={classes["rows"]}>
|
<div className={classes["rows"]}>
|
||||||
<Toggle size={EToggleSize.MD} />
|
<Toggle size={EToggleSize.MD} />
|
||||||
@ -370,6 +443,7 @@ export default function DesignSystem() {
|
|||||||
<Tag color={ETagColor.SUCCESS} variant={ETagVariant.REGULAR} label="Success" />
|
<Tag color={ETagColor.SUCCESS} variant={ETagVariant.REGULAR} label="Success" />
|
||||||
<Tag color={ETagColor.WARNING} variant={ETagVariant.REGULAR} label="Warning" />
|
<Tag color={ETagColor.WARNING} variant={ETagVariant.REGULAR} label="Warning" />
|
||||||
<Tag color={ETagColor.ERROR} variant={ETagVariant.REGULAR} label="Error" />
|
<Tag color={ETagColor.ERROR} variant={ETagVariant.REGULAR} label="Error" />
|
||||||
|
<Tag color={ETagColor.NEUTRAL} variant={ETagVariant.REGULAR} label="Envoyé" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Table Tags</Typography>
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Table Tags</Typography>
|
||||||
@ -378,6 +452,7 @@ export default function DesignSystem() {
|
|||||||
<Tag color={ETagColor.SUCCESS} variant={ETagVariant.SEMI_BOLD} label="SUCCESS" />
|
<Tag color={ETagColor.SUCCESS} variant={ETagVariant.SEMI_BOLD} label="SUCCESS" />
|
||||||
<Tag color={ETagColor.WARNING} variant={ETagVariant.SEMI_BOLD} label="WARNING" />
|
<Tag color={ETagColor.WARNING} variant={ETagVariant.SEMI_BOLD} label="WARNING" />
|
||||||
<Tag color={ETagColor.ERROR} variant={ETagVariant.SEMI_BOLD} label="ERROR" />
|
<Tag color={ETagColor.ERROR} variant={ETagVariant.SEMI_BOLD} label="ERROR" />
|
||||||
|
<Tag color={ETagColor.NEUTRAL} variant={ETagVariant.SEMI_BOLD} label="ENVOYÉ" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Table</Typography>
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Table</Typography>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
.cancel-button {
|
.cancel-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: 32px;
|
margin-right: var(--spacing-md, 16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $screen-m) {
|
@media (max-width: $screen-m) {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
.add-document-form-container {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: var(--spacing-md, 16px);
|
||||||
|
width: 566px;
|
||||||
|
|
||||||
.radiobox-container {
|
.radiobox-container {
|
||||||
> :not(:last-child) {
|
display: flex;
|
||||||
margin-bottom: 16px;
|
flex-direction: column;
|
||||||
}
|
gap: var(--spacing-xs, 8px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/Dr
|
|||||||
import AutocompleteMultiSelectField from "@Front/Components/DesignSystem/Form/AutocompleteMultiSelectField";
|
import AutocompleteMultiSelectField from "@Front/Components/DesignSystem/Form/AutocompleteMultiSelectField";
|
||||||
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||||
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
|
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||||
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
||||||
import { DocumentType, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
import { DocumentType, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||||
import { ChangeEvent, useCallback, useEffect, useState } from "react";
|
import { ChangeEvent, useCallback, useEffect, useState } from "react";
|
||||||
@ -117,15 +117,13 @@ export default function ParameterDocuments(props: IProps) {
|
|||||||
}, [getAvailableDocuments, props.folder]);
|
}, [getAvailableDocuments, props.folder]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Confirm
|
<Modal
|
||||||
isOpen={props.isCreateDocumentModalVisible}
|
isOpen={props.isCreateDocumentModalVisible}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
onAccept={addDocument}
|
firstButton={{ children: "Annuler", onClick: handleClose }}
|
||||||
closeBtn
|
secondButton={{ children: "Ajouter", onClick: addDocument }}
|
||||||
header={"Ajouter des documents demandables"}
|
title={"Ajouter un document"}>
|
||||||
cancelText={"Annuler"}
|
<div className={classes["root"]}>
|
||||||
confirmText={"Ajouter"}>
|
|
||||||
<div className={classes["add-document-form-container"]}>
|
|
||||||
<div className={classes["radiobox-container"]}>
|
<div className={classes["radiobox-container"]}>
|
||||||
<RadioBox
|
<RadioBox
|
||||||
name="document"
|
name="document"
|
||||||
@ -143,6 +141,7 @@ export default function ParameterDocuments(props: IProps) {
|
|||||||
label="Créer un document"
|
label="Créer un document"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{addOrEditDocument === "add" && (
|
{addOrEditDocument === "add" && (
|
||||||
<>
|
<>
|
||||||
<TextField name="document_name" placeholder="Nom du document à ajouter" onChange={onDocumentNameChange} />
|
<TextField name="document_name" placeholder="Nom du document à ajouter" onChange={onDocumentNameChange} />
|
||||||
@ -162,6 +161,6 @@ export default function ParameterDocuments(props: IProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Confirm>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
.buttons-container {
|
.buttons-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 32px;
|
gap: var(--spacing-md, 16px);
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
|
|
||||||
@media (max-width: $screen-s) {
|
@media (max-width: $screen-s) {
|
||||||
|
@ -13,14 +13,14 @@ import React, { useCallback, useEffect, useState } from "react";
|
|||||||
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import ParameterDocuments from "./ParameterDocuments";
|
import ParameterDocuments from "./ParameterDocuments";
|
||||||
import { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
import { IOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
||||||
import backgroundImage from "@Assets/images/background_refonte.svg";
|
import backgroundImage from "@Assets/images/background_refonte.svg";
|
||||||
|
|
||||||
export default function AskDocuments() {
|
export default function AskDocuments() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
let { folderUid, customerUid } = router.query;
|
let { folderUid, customerUid } = router.query;
|
||||||
const [isCreateDocumentModalVisible, setIsCreateDocumentModalVisible] = useState<boolean>(false);
|
const [isCreateDocumentModalVisible, setIsCreateDocumentModalVisible] = useState<boolean>(false);
|
||||||
const [documentTypes, setDocumentTypes] = useState<IOptionOld[]>([]);
|
const [documentTypes, setDocumentTypes] = useState<IOption[]>([]);
|
||||||
const [folder, setFolder] = useState<OfficeFolder | null>(null);
|
const [folder, setFolder] = useState<OfficeFolder | null>(null);
|
||||||
|
|
||||||
const closeModal = () => setIsCreateDocumentModalVisible(false);
|
const closeModal = () => setIsCreateDocumentModalVisible(false);
|
||||||
@ -62,7 +62,7 @@ export default function AskDocuments() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getAvailableDocuments = useCallback(
|
const getAvailableDocuments = useCallback(
|
||||||
async (folder: OfficeFolder): Promise<IOptionOld[]> => {
|
async (folder: OfficeFolder): Promise<IOption[]> => {
|
||||||
// Getting already asked documents UIDs in an array
|
// Getting already asked documents UIDs in an array
|
||||||
const userDocumentTypesUids = folder
|
const userDocumentTypesUids = folder
|
||||||
.documents!.filter((document) => document.depositor!.uid! === customerUid!)
|
.documents!.filter((document) => document.depositor!.uid! === customerUid!)
|
||||||
@ -81,7 +81,7 @@ export default function AskDocuments() {
|
|||||||
if (!documentTypes) return [];
|
if (!documentTypes) return [];
|
||||||
|
|
||||||
// Else, return an array document types formatted as IOPtions
|
// Else, return an array document types formatted as IOPtions
|
||||||
const documentTypesOptions: IOptionOld[] = documentTypes.map((documentType) => {
|
const documentTypesOptions: IOption[] = documentTypes.map((documentType) => {
|
||||||
return {
|
return {
|
||||||
label: documentType!.name!,
|
label: documentType!.name!,
|
||||||
value: documentType!.uid!,
|
value: documentType!.uid!,
|
||||||
@ -155,7 +155,7 @@ export default function AskDocuments() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={classes["add-document-container"]}>
|
<div className={classes["add-document-container"]}>
|
||||||
<Button
|
<Button
|
||||||
rightIcon={<PlusIcon style={{ transform: "rotate(180deg)", width: "22px", height: "22px" }} />}
|
leftIcon={<PlusIcon style={{ transform: "rotate(180deg)", width: "22px", height: "22px" }} />}
|
||||||
variant={EButtonVariant.PRIMARY}
|
variant={EButtonVariant.PRIMARY}
|
||||||
styletype={EButtonstyletype.OUTLINED}
|
styletype={EButtonstyletype.OUTLINED}
|
||||||
size={EButtonSize.MD}
|
size={EButtonSize.MD}
|
||||||
@ -169,7 +169,7 @@ export default function AskDocuments() {
|
|||||||
Annuler
|
Annuler
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
<Button type="submit">Valider</Button>
|
<Button type="submit">Envoyer la demande</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
padding: var(--spacing-4) 142px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
.table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-filter{
|
||||||
|
width: 472px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-m) {
|
||||||
|
padding: var(--spacing-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
padding: var(--spacing-2);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
|
||||||
|
import DocumentReminders from "@Front/Api/LeCoffreApi/Notary/DocumentReminders/DocumentReminders";
|
||||||
|
import Dropdown from "@Front/Components/DesignSystem/Dropdown";
|
||||||
|
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
|
||||||
|
import Table from "@Front/Components/DesignSystem/Table";
|
||||||
|
import { IHead, IRowProps } from "@Front/Components/DesignSystem/Table/MuiTable";
|
||||||
|
import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||||
|
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import Customer from "le-coffre-resources/dist/Customer";
|
||||||
|
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||||
|
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
type IProps = {};
|
||||||
|
|
||||||
|
const tradDocumentStatus: Record<EDocumentStatus, string> = {
|
||||||
|
[EDocumentStatus.ASKED]: "DEMANDÉ",
|
||||||
|
[EDocumentStatus.DEPOSITED]: "À VALIDER",
|
||||||
|
[EDocumentStatus.VALIDATED]: "VALIDÉ",
|
||||||
|
[EDocumentStatus.REFUSED]: "REFUSÉ",
|
||||||
|
};
|
||||||
|
|
||||||
|
const header: readonly IHead[] = [
|
||||||
|
{
|
||||||
|
key: "remindedAt",
|
||||||
|
title: "Date de relance",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "customer",
|
||||||
|
title: "Client",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "document_type",
|
||||||
|
title: "Type de document",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "statut",
|
||||||
|
title: "Satut",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function DocumentsReminderHistory(props: IProps) {
|
||||||
|
const [reminders, setReminders] = useState<DocumentReminder[] | null>(null);
|
||||||
|
const [customers, setCustomers] = useState<Customer[] | null>(null);
|
||||||
|
const [customerOption, setCustomerOption] = useState<IOption | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
let { folderUid } = router.query;
|
||||||
|
|
||||||
|
const fetchReminders = useCallback(() => {
|
||||||
|
DocumentReminders.getInstance()
|
||||||
|
.get({
|
||||||
|
...(customerOption && customerOption.id !== "tous" && { where: { document: { depositor: { uid: customerOption.id } } } }),
|
||||||
|
include: {
|
||||||
|
document: {
|
||||||
|
include: {
|
||||||
|
depositor: {
|
||||||
|
include: {
|
||||||
|
contact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
document_type: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { reminder_date: "desc" },
|
||||||
|
})
|
||||||
|
.then((reminders) => setReminders(reminders))
|
||||||
|
.catch((e) => console.warn(e));
|
||||||
|
}, [customerOption]);
|
||||||
|
|
||||||
|
const fetchCustomers = useCallback(async () => {
|
||||||
|
if (!folderUid) return;
|
||||||
|
Customers.getInstance()
|
||||||
|
.get({
|
||||||
|
where: {
|
||||||
|
office_folders: {
|
||||||
|
some: {
|
||||||
|
uid: folderUid as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(setCustomers)
|
||||||
|
.catch(console.warn);
|
||||||
|
}, [folderUid]);
|
||||||
|
|
||||||
|
const customersOptions: IOption[] = useMemo(() => {
|
||||||
|
let options = [
|
||||||
|
{
|
||||||
|
id: "tous",
|
||||||
|
label: "Tous",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
customers?.forEach((customer) => {
|
||||||
|
options.push({
|
||||||
|
id: customer.uid ?? "",
|
||||||
|
label: `${customer.contact?.first_name} ${customer.contact?.last_name}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}, [customers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchReminders();
|
||||||
|
fetchCustomers();
|
||||||
|
}, [customerOption, customersOptions, fetchCustomers, fetchReminders]);
|
||||||
|
|
||||||
|
const onSelectionChange = useCallback((option: IOption | null) => {
|
||||||
|
setCustomerOption(option ?? null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaultTemplate title={"Historique des relances de documents"} isPadding={false}>
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<BackArrow
|
||||||
|
text="Retour"
|
||||||
|
url={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string)}
|
||||||
|
/>
|
||||||
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
Historique des relances de documents
|
||||||
|
</Typography>
|
||||||
|
<Dropdown
|
||||||
|
className={classes["customer-filter"]}
|
||||||
|
options={customersOptions}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
selectedOption={customerOption ?? customersOptions?.[0]}
|
||||||
|
label="Client"
|
||||||
|
/>
|
||||||
|
<Table className={classes["table"]} header={header} rows={buildRows(reminders)} />
|
||||||
|
</div>
|
||||||
|
</DefaultTemplate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRows(reminders: DocumentReminder[] | null): IRowProps[] {
|
||||||
|
if (!reminders) return [];
|
||||||
|
return reminders.map((reminder) => ({
|
||||||
|
key: reminder.uid ?? "",
|
||||||
|
remindedAt: new Date(reminder.reminder_date!).toLocaleDateString(),
|
||||||
|
customer: `${reminder.document?.depositor?.contact?.first_name} ${reminder.document?.depositor?.contact?.last_name}`,
|
||||||
|
document_type: reminder.document?.document_type?.name,
|
||||||
|
statut: getTag(reminder.document?.document_status as EDocumentStatus),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTag(status: EDocumentStatus) {
|
||||||
|
switch (status) {
|
||||||
|
case EDocumentStatus.ASKED:
|
||||||
|
return <Tag label={tradDocumentStatus[status]} color={ETagColor.INFO} variant={ETagVariant.SEMI_BOLD} />;
|
||||||
|
case EDocumentStatus.DEPOSITED:
|
||||||
|
return <Tag label={tradDocumentStatus[status]} color={ETagColor.WARNING} variant={ETagVariant.SEMI_BOLD} />;
|
||||||
|
case EDocumentStatus.VALIDATED:
|
||||||
|
return <Tag label={tradDocumentStatus[status]} color={ETagColor.SUCCESS} variant={ETagVariant.SEMI_BOLD} />;
|
||||||
|
case EDocumentStatus.REFUSED:
|
||||||
|
return <Tag label={tradDocumentStatus[status]} color={ETagColor.ERROR} variant={ETagVariant.SEMI_BOLD} />;
|
||||||
|
default:
|
||||||
|
return <Tag label={tradDocumentStatus[status]} color={ETagColor.INFO} variant={ETagVariant.SEMI_BOLD} />;
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: fit-content;
|
|
||||||
padding: var(--spacing-md, 16px);
|
padding: var(--spacing-md, 16px);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-md, 16px);
|
gap: var(--spacing-md, 16px);
|
||||||
background: var(--primary-weak-higlight, #e5eefa);
|
background: var(--primary-weak-higlight, #e5eefa);
|
||||||
min-width: 300px;
|
width: 300px;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -6,16 +6,15 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
|
|||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
import useOpenable from "@Front/Hooks/useOpenable";
|
import useOpenable from "@Front/Hooks/useOpenable";
|
||||||
import { PencilSquareIcon, TrashIcon, UsersIcon } from "@heroicons/react/24/outline";
|
import { PencilSquareIcon, TrashIcon, UsersIcon } from "@heroicons/react/24/outline";
|
||||||
import { Note } from "le-coffre-resources/dist/Customer";
|
import Customer, { Note } from "le-coffre-resources/dist/Customer";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import { ICustomer } from "..";
|
|
||||||
import { AnchorStatus } from "../..";
|
import { AnchorStatus } from "../..";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import DeleteCustomerModal from "./DeleteCustomerModal";
|
import DeleteCustomerModal from "./DeleteCustomerModal";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
customer: ICustomer;
|
customer: Customer;
|
||||||
anchorStatus: AnchorStatus;
|
anchorStatus: AnchorStatus;
|
||||||
folderUid: string | undefined;
|
folderUid: string | undefined;
|
||||||
customerNote: Note | null;
|
customerNote: Note | null;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
||||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||||
|
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
|
|
||||||
@ -7,7 +8,6 @@ type IProps = {
|
|||||||
documentUid: string;
|
documentUid: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
|
||||||
onDeleteSuccess: (uid: string) => void;
|
onDeleteSuccess: (uid: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ export default function DeleteAskedDocumentModal(props: IProps) {
|
|||||||
Documents.getInstance()
|
Documents.getInstance()
|
||||||
.delete(documentUid)
|
.delete(documentUid)
|
||||||
.then(() => onDeleteSuccess(documentUid))
|
.then(() => onDeleteSuccess(documentUid))
|
||||||
|
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
|
||||||
.then(onClose)
|
.then(onClose)
|
||||||
.catch((error) => console.warn(error)),
|
.catch((error) => console.warn(error)),
|
||||||
[documentUid, onClose, onDeleteSuccess],
|
[documentUid, onClose, onDeleteSuccess],
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||||
|
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||||
|
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||||
|
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
documentUid: string;
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
onDeleteSuccess: (uid: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DeleteSentDocumentModal(props: IProps) {
|
||||||
|
const { isOpen, onClose, documentUid, onDeleteSuccess } = props;
|
||||||
|
|
||||||
|
const onDelete = useCallback(
|
||||||
|
() =>
|
||||||
|
DocumentsNotary.getInstance()
|
||||||
|
.delete(documentUid)
|
||||||
|
.then(() => onDeleteSuccess(documentUid))
|
||||||
|
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
|
||||||
|
.then(onClose)
|
||||||
|
.catch((error) => console.warn(error)),
|
||||||
|
[documentUid, onClose, onDeleteSuccess],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
title={"Supprimer l’envoi de document ?"}
|
||||||
|
firstButton={{ children: "Annuler", onClick: onClose }}
|
||||||
|
secondButton={{ children: "Oui, Supprimer", onClick: onDelete }}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_LIGHT}>Cette action annulera l'envoi du document.</Typography>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -11,4 +11,11 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
||||||
|
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||||
import Files from "@Front/Api/LeCoffreApi/Notary/Files/Files";
|
import Files from "@Front/Api/LeCoffreApi/Notary/Files/Files";
|
||||||
|
import FilesNotary from "@Front/Api/LeCoffreApi/Notary/FilesNotary/Files";
|
||||||
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
||||||
import IconButton from "@Front/Components/DesignSystem/IconButton";
|
import IconButton from "@Front/Components/DesignSystem/IconButton";
|
||||||
import Table from "@Front/Components/DesignSystem/Table";
|
import Table from "@Front/Components/DesignSystem/Table";
|
||||||
@ -8,38 +11,23 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
|
|||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
import useOpenable from "@Front/Hooks/useOpenable";
|
import useOpenable from "@Front/Hooks/useOpenable";
|
||||||
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline";
|
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { useMediaQuery } from "@mui/material";
|
||||||
import { Document } from "le-coffre-resources/dist/Customer";
|
import { Document } from "le-coffre-resources/dist/Customer";
|
||||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||||
|
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import NoDocument from "../NoDocument";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal";
|
import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal";
|
||||||
|
import DeleteSentDocumentModal from "./DeleteSentDocumentModal";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
documents: Document[];
|
customerUid: string;
|
||||||
folderUid: string;
|
folderUid: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const header: readonly IHead[] = [
|
|
||||||
{
|
|
||||||
key: "document_type",
|
|
||||||
title: "Type de document",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "document_status",
|
|
||||||
title: "Statut",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "created_at",
|
|
||||||
title: "Demandé le",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "actions",
|
|
||||||
title: "Actions",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const tradDocumentStatus: Record<EDocumentStatus, string> = {
|
const tradDocumentStatus: Record<EDocumentStatus, string> = {
|
||||||
[EDocumentStatus.ASKED]: "Demandé",
|
[EDocumentStatus.ASKED]: "Demandé",
|
||||||
[EDocumentStatus.DEPOSITED]: "À valider",
|
[EDocumentStatus.DEPOSITED]: "À valider",
|
||||||
@ -48,23 +36,58 @@ const tradDocumentStatus: Record<EDocumentStatus, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function DocumentTables(props: IProps) {
|
export default function DocumentTables(props: IProps) {
|
||||||
const { documents: documentsProps, folderUid } = props;
|
const { folderUid, customerUid } = props;
|
||||||
const [documents, setDocuments] = useState<Document[]>(documentsProps);
|
const [documents, setDocuments] = useState<Document[]>([]);
|
||||||
const [documentUid, setDocumentUid] = useState<string | null>(null);
|
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
|
||||||
|
const [focusedDocumentUid, setFocusedDocumentUid] = useState<string | null>(null);
|
||||||
|
|
||||||
const deleteAskedOocumentModal = useOpenable();
|
const isMobile = useMediaQuery("(max-width:524px)");
|
||||||
|
|
||||||
|
const deleteAskedDocumentModal = useOpenable();
|
||||||
|
const deleteSentDocumentModal = useOpenable();
|
||||||
|
|
||||||
|
const fetchDocuments = useCallback(
|
||||||
|
() =>
|
||||||
|
Documents.getInstance()
|
||||||
|
.get({
|
||||||
|
where: { folder: { uid: folderUid }, depositor: { uid: customerUid } },
|
||||||
|
include: { files: true, document_type: true },
|
||||||
|
})
|
||||||
|
.then(setDocuments)
|
||||||
|
.catch(console.warn),
|
||||||
|
[customerUid, folderUid],
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchDocumentsNotary = useCallback(
|
||||||
|
() =>
|
||||||
|
DocumentsNotary.getInstance()
|
||||||
|
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
|
||||||
|
.then(setDocumentsNotary)
|
||||||
|
.catch(console.warn),
|
||||||
|
[customerUid, folderUid],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDocuments(documentsProps);
|
fetchDocuments();
|
||||||
}, [documentsProps]);
|
fetchDocumentsNotary();
|
||||||
|
}, [fetchDocuments, fetchDocumentsNotary]);
|
||||||
|
|
||||||
const openDeleteAskedDocumentModal = useCallback(
|
const openDeleteAskedDocumentModal = useCallback(
|
||||||
(uid: string | undefined) => {
|
(uid: string | undefined) => {
|
||||||
if (!uid) return;
|
if (!uid) return;
|
||||||
setDocumentUid(uid);
|
setFocusedDocumentUid(uid);
|
||||||
deleteAskedOocumentModal.open();
|
deleteAskedDocumentModal.open();
|
||||||
},
|
},
|
||||||
[deleteAskedOocumentModal],
|
[deleteAskedDocumentModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
const openDeleteSentDocumentModal = useCallback(
|
||||||
|
(uid: string | undefined) => {
|
||||||
|
if (!uid) return;
|
||||||
|
setFocusedDocumentUid(uid);
|
||||||
|
deleteSentDocumentModal.open();
|
||||||
|
},
|
||||||
|
[deleteSentDocumentModal],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDownload = useCallback((doc: Document) => {
|
const onDownload = useCallback((doc: Document) => {
|
||||||
@ -84,6 +107,23 @@ export default function DocumentTables(props: IProps) {
|
|||||||
.catch((e) => console.warn(e));
|
.catch((e) => console.warn(e));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onDownloadFileNotary = useCallback((doc: DocumentNotary) => {
|
||||||
|
const file = doc.files?.[0];
|
||||||
|
if (!file || !file?.uid) return;
|
||||||
|
|
||||||
|
return FilesNotary.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(
|
const askedDocuments: IRowProps[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
documents
|
documents
|
||||||
@ -91,16 +131,29 @@ export default function DocumentTables(props: IProps) {
|
|||||||
if (document.document_status !== EDocumentStatus.ASKED) return null;
|
if (document.document_status !== EDocumentStatus.ASKED) return null;
|
||||||
return {
|
return {
|
||||||
key: document.uid,
|
key: document.uid,
|
||||||
document_type: document.document_type?.name ?? "_",
|
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||||
document_status: (
|
document_status: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: (
|
||||||
<Tag
|
<Tag
|
||||||
color={ETagColor.INFO}
|
color={ETagColor.INFO}
|
||||||
variant={ETagVariant.SEMI_BOLD}
|
variant={ETagVariant.SEMI_BOLD}
|
||||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
|
},
|
||||||
actions: <IconButton icon={<TrashIcon onClick={() => openDeleteAskedDocumentModal(document.uid)} />} />,
|
date: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
sx: { width: 76 },
|
||||||
|
content: (
|
||||||
|
<div className={classes["actions"]}>
|
||||||
|
<IconButton icon={<TrashIcon onClick={() => openDeleteAskedDocumentModal(document.uid)} />} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((document) => document !== null) as IRowProps[],
|
.filter((document) => document !== null) as IRowProps[],
|
||||||
@ -114,16 +167,25 @@ export default function DocumentTables(props: IProps) {
|
|||||||
if (document.document_status !== EDocumentStatus.DEPOSITED) return null;
|
if (document.document_status !== EDocumentStatus.DEPOSITED) return null;
|
||||||
return {
|
return {
|
||||||
key: document.uid,
|
key: document.uid,
|
||||||
document_type: document.document_type?.name ?? "_",
|
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||||
document_status: (
|
document_status: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: (
|
||||||
<Tag
|
<Tag
|
||||||
color={ETagColor.WARNING}
|
color={ETagColor.WARNING}
|
||||||
variant={ETagVariant.SEMI_BOLD}
|
variant={ETagVariant.SEMI_BOLD}
|
||||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
|
},
|
||||||
actions: (
|
date: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
sx: { width: 76 },
|
||||||
|
content: (
|
||||||
|
<div className={classes["actions"]}>
|
||||||
<Link
|
<Link
|
||||||
href={Module.getInstance()
|
href={Module.getInstance()
|
||||||
.get()
|
.get()
|
||||||
@ -131,7 +193,9 @@ export default function DocumentTables(props: IProps) {
|
|||||||
.replace("[documentUid]", document.uid ?? "")}>
|
.replace("[documentUid]", document.uid ?? "")}>
|
||||||
<IconButton icon={<EyeIcon />} />
|
<IconButton icon={<EyeIcon />} />
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((document) => document !== null) as IRowProps[],
|
.filter((document) => document !== null) as IRowProps[],
|
||||||
@ -145,16 +209,24 @@ export default function DocumentTables(props: IProps) {
|
|||||||
if (document.document_status !== EDocumentStatus.VALIDATED) return null;
|
if (document.document_status !== EDocumentStatus.VALIDATED) return null;
|
||||||
return {
|
return {
|
||||||
key: document.uid,
|
key: document.uid,
|
||||||
document_type: document.document_type?.name ?? "_",
|
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||||
document_status: (
|
document_status: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: (
|
||||||
<Tag
|
<Tag
|
||||||
color={ETagColor.SUCCESS}
|
color={ETagColor.SUCCESS}
|
||||||
variant={ETagVariant.SEMI_BOLD}
|
variant={ETagVariant.SEMI_BOLD}
|
||||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
|
},
|
||||||
actions: (
|
date: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
sx: { width: 76 },
|
||||||
|
content: (
|
||||||
<div className={classes["actions"]}>
|
<div className={classes["actions"]}>
|
||||||
<Link
|
<Link
|
||||||
href={Module.getInstance()
|
href={Module.getInstance()
|
||||||
@ -166,6 +238,7 @@ export default function DocumentTables(props: IProps) {
|
|||||||
<IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} />
|
<IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((document) => document !== null) as IRowProps[],
|
.filter((document) => document !== null) as IRowProps[],
|
||||||
@ -179,15 +252,21 @@ export default function DocumentTables(props: IProps) {
|
|||||||
if (document.document_status !== EDocumentStatus.REFUSED) return null;
|
if (document.document_status !== EDocumentStatus.REFUSED) return null;
|
||||||
return {
|
return {
|
||||||
key: document.uid,
|
key: document.uid,
|
||||||
document_type: document.document_type?.name ?? "_",
|
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||||
document_status: (
|
document_status: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: (
|
||||||
<Tag
|
<Tag
|
||||||
color={ETagColor.ERROR}
|
color={ETagColor.ERROR}
|
||||||
variant={ETagVariant.SEMI_BOLD}
|
variant={ETagVariant.SEMI_BOLD}
|
||||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
|
},
|
||||||
|
date: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||||
|
},
|
||||||
actions: "",
|
actions: "",
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -195,19 +274,46 @@ export default function DocumentTables(props: IProps) {
|
|||||||
[documents],
|
[documents],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sentDocuments: IRowProps[] = useMemo(
|
||||||
|
() =>
|
||||||
|
documentsNotary
|
||||||
|
.map((document) => {
|
||||||
|
return {
|
||||||
|
key: document.uid,
|
||||||
|
document_type: {
|
||||||
|
sx: { width: 400 },
|
||||||
|
content: formatName(document.files?.[0]?.file_name?.split(".")?.[0] ?? "") || "_",
|
||||||
|
},
|
||||||
|
document_status: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: <Tag color={ETagColor.NEUTRAL} variant={ETagVariant.SEMI_BOLD} label={"ENVOYÉ"} />,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
sx: { width: 107 },
|
||||||
|
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
sx: { width: 76 },
|
||||||
|
content: (
|
||||||
|
<div className={classes["actions"]}>
|
||||||
|
<IconButton onClick={() => onDownloadFileNotary(document)} icon={<ArrowDownTrayIcon />} />
|
||||||
|
<IconButton icon={<TrashIcon onClick={() => openDeleteSentDocumentModal(document.uid)} />} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((document) => document !== null) as IRowProps[],
|
||||||
|
[documentsNotary, onDownloadFileNotary, openDeleteSentDocumentModal],
|
||||||
|
);
|
||||||
|
|
||||||
const progress = useMemo(() => {
|
const progress = useMemo(() => {
|
||||||
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length + refusedDocuments.length;
|
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length + refusedDocuments.length;
|
||||||
if (total === 0) return 0;
|
if (total === 0) return 0;
|
||||||
return (validatedDocuments.length / total) * 100;
|
return (validatedDocuments.length / total) * 100;
|
||||||
}, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
|
}, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
if (documents.length === 0 && documentsNotary.length === 0) return <NoDocument />;
|
||||||
(documentUid: string) => {
|
|
||||||
setDocuments(documents.filter((document) => document.uid !== documentUid));
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
[documents],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
@ -217,18 +323,68 @@ export default function DocumentTables(props: IProps) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<CircleProgress percentage={progress} />
|
<CircleProgress percentage={progress} />
|
||||||
</div>
|
</div>
|
||||||
{askedDocuments.length > 0 && <Table header={header} rows={askedDocuments} />}
|
{askedDocuments.length > 0 && <Table header={getHeader("Demandé le", isMobile)} rows={askedDocuments} />}
|
||||||
{toValidateDocuments.length > 0 && <Table header={header} rows={toValidateDocuments} />}
|
{toValidateDocuments.length > 0 && <Table header={getHeader("Déposé le", isMobile)} rows={toValidateDocuments} />}
|
||||||
{validatedDocuments.length > 0 && <Table header={header} rows={validatedDocuments} />}
|
{validatedDocuments.length > 0 && <Table header={getHeader("Validé le", isMobile)} rows={validatedDocuments} />}
|
||||||
{refusedDocuments.length > 0 && <Table header={header} rows={refusedDocuments} />}
|
{refusedDocuments.length > 0 && <Table header={getHeader("Demandé le", isMobile)} rows={refusedDocuments} />}
|
||||||
{documentUid && (
|
{sentDocuments.length > 0 && <Table header={getHeader("Envoyé le", isMobile)} rows={sentDocuments} />}
|
||||||
|
{focusedDocumentUid && (
|
||||||
|
<>
|
||||||
<DeleteAskedDocumentModal
|
<DeleteAskedDocumentModal
|
||||||
isOpen={deleteAskedOocumentModal.isOpen}
|
isOpen={deleteAskedDocumentModal.isOpen}
|
||||||
onClose={deleteAskedOocumentModal.close}
|
onClose={deleteAskedDocumentModal.close}
|
||||||
onDeleteSuccess={handleDelete}
|
onDeleteSuccess={fetchDocuments}
|
||||||
documentUid={documentUid}
|
documentUid={focusedDocumentUid}
|
||||||
/>
|
/>
|
||||||
|
<DeleteSentDocumentModal
|
||||||
|
isOpen={deleteSentDocumentModal.isOpen}
|
||||||
|
onClose={deleteSentDocumentModal.close}
|
||||||
|
onDeleteSuccess={fetchDocumentsNotary}
|
||||||
|
documentUid={focusedDocumentUid}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHeader(dateColumnTitle: string, isMobile: boolean): IHead[] {
|
||||||
|
if (isMobile) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: "document_type",
|
||||||
|
title: "Type de document",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "document_status",
|
||||||
|
title: "Statut",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "actions",
|
||||||
|
title: "Action",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: "document_type",
|
||||||
|
title: "Type de document",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "document_status",
|
||||||
|
title: "Statut",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "date",
|
||||||
|
title: dateColumnTitle,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "actions",
|
||||||
|
title: "Action",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatName(text: string): string {
|
||||||
|
return text.replace(/[^a-zA-Z0-9 ]/g, "");
|
||||||
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md, 16px);
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
|
||||||
|
import CheckBox from "@Front/Components/DesignSystem/CheckBox";
|
||||||
|
import { IOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
||||||
|
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||||
|
import Separator, { ESeperatorColor } from "@Front/Components/DesignSystem/Separator";
|
||||||
|
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import Customer from "le-coffre-resources/dist/Customer";
|
||||||
|
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||||
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
onRemindSuccess: () => void;
|
||||||
|
customer: Customer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ReminderModal(props: IProps) {
|
||||||
|
const { isOpen, onClose, onRemindSuccess, customer } = props;
|
||||||
|
const [selectedOptions, setSelectedOptions] = useState<IOption[]>([]);
|
||||||
|
const [isAllSelected, setIsAllSelected] = useState(false);
|
||||||
|
|
||||||
|
const onRemind = useCallback(() => {
|
||||||
|
Customers.getInstance()
|
||||||
|
.sendReminder(customer.uid!, selectedOptions.map((option) => option.value) as string[])
|
||||||
|
.then(onRemindSuccess)
|
||||||
|
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "La relance a été envoyée avec succès." }))
|
||||||
|
.then(onClose);
|
||||||
|
}, [customer.uid, onClose, onRemindSuccess, selectedOptions]);
|
||||||
|
|
||||||
|
const documentsOptions: IOption[] = useMemo(
|
||||||
|
() =>
|
||||||
|
customer.documents
|
||||||
|
?.filter((document) => document.document_status !== EDocumentStatus.VALIDATED)
|
||||||
|
.map((document) => {
|
||||||
|
return {
|
||||||
|
label: document.document_type?.name ?? "",
|
||||||
|
value: document.uid ?? "",
|
||||||
|
};
|
||||||
|
}) ?? [],
|
||||||
|
[customer],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOnChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value, checked } = e.target;
|
||||||
|
const optionSelected = documentsOptions.find((option) => option.value === value);
|
||||||
|
if (checked && optionSelected) {
|
||||||
|
setSelectedOptions((prev) => [...prev, optionSelected]);
|
||||||
|
} else {
|
||||||
|
setSelectedOptions((prev) => prev.filter((option) => option.value !== value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[documentsOptions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectAll = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { checked } = e.target;
|
||||||
|
if (checked) {
|
||||||
|
setSelectedOptions(documentsOptions);
|
||||||
|
setIsAllSelected(true);
|
||||||
|
} else {
|
||||||
|
setSelectedOptions([]);
|
||||||
|
setIsAllSelected(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[documentsOptions],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
title={`Relancer votre client ${customer.contact?.last_name}`}
|
||||||
|
firstButton={{ children: "Annuler", onClick: onClose }}
|
||||||
|
secondButton={{ children: "Relancer", onClick: onRemind }}>
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Sélectionnez le(s) document(s) pour lequel vous souhaitez relancer le client.
|
||||||
|
</Typography>
|
||||||
|
<CheckBox
|
||||||
|
option={{ label: "Tous les documents", value: "all-documents" }}
|
||||||
|
checked={isAllSelected}
|
||||||
|
onChange={handleSelectAll}
|
||||||
|
/>
|
||||||
|
<Separator color={ESeperatorColor.LIGHT} />
|
||||||
|
{documentsOptions.map((option) => (
|
||||||
|
<CheckBox
|
||||||
|
key={option.value as string}
|
||||||
|
option={option}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
checked={isAllSelected}
|
||||||
|
disabled={isAllSelected}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md, 32px);
|
||||||
|
|
||||||
|
.reminder-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
import DocumentReminders from "@Front/Api/LeCoffreApi/Notary/DocumentReminders/DocumentReminders";
|
||||||
|
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
|
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import useOpenable from "@Front/Hooks/useOpenable";
|
||||||
|
import { ClockIcon, EnvelopeIcon } from "@heroicons/react/24/outline";
|
||||||
|
import Customer from "le-coffre-resources/dist/Customer";
|
||||||
|
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import ReminderModal from "./ReminderModal";
|
||||||
|
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
customer: Customer;
|
||||||
|
isAnchored: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EmailReminder(props: IProps) {
|
||||||
|
const { customer, isAnchored } = props;
|
||||||
|
const [reminders, setReminders] = useState<DocumentReminder[] | null>(null);
|
||||||
|
const { isOpen, open, close } = useOpenable();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
let { folderUid } = router.query;
|
||||||
|
|
||||||
|
const fetchReminders = useCallback(async () => {
|
||||||
|
if (!customer.uid || !folderUid) return;
|
||||||
|
DocumentReminders.getInstance()
|
||||||
|
.get({
|
||||||
|
where: { document: { depositor: { uid: customer.uid }, folder: { uid: folderUid } } },
|
||||||
|
include: { document: "true" },
|
||||||
|
orderBy: { reminder_date: "desc" },
|
||||||
|
})
|
||||||
|
.then((reminders) => setReminders(reminders))
|
||||||
|
.catch((e) => console.warn(e));
|
||||||
|
}, [customer.uid, folderUid]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchReminders();
|
||||||
|
}, [fetchReminders]);
|
||||||
|
|
||||||
|
const remindersLength = useMemo(() => {
|
||||||
|
// Vérifie que la liste `reminders` n'est pas vide ou null
|
||||||
|
if (!reminders || reminders.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remindersGroupByDate = reminders.reduce((acc, reminder) => {
|
||||||
|
// Vérifie si `reminder_date` est bien défini
|
||||||
|
if (!reminder.reminder_date) return acc;
|
||||||
|
|
||||||
|
// Normalise la date à la seconde près
|
||||||
|
const reminderDate = new Date(reminder.reminder_date).setMilliseconds(0);
|
||||||
|
|
||||||
|
// Initialise ou incrémente le compteur
|
||||||
|
acc[reminderDate] = (acc[reminderDate] || 0) + 1;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<number, number>);
|
||||||
|
|
||||||
|
// Retourne la longueur des clés, représentant le nombre de dates uniques
|
||||||
|
return Object.keys(remindersGroupByDate).length;
|
||||||
|
}, [reminders]);
|
||||||
|
|
||||||
|
const doesCustomerHaveNotValidatedDocuments = useMemo(
|
||||||
|
() => customer.documents && customer.documents.some((document) => document.document_status !== EDocumentStatus.VALIDATED),
|
||||||
|
[customer.documents],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
{!isAnchored && (
|
||||||
|
<Button
|
||||||
|
onClick={open}
|
||||||
|
rightIcon={<EnvelopeIcon />}
|
||||||
|
variant={EButtonVariant.PRIMARY}
|
||||||
|
styletype={EButtonstyletype.OUTLINED}
|
||||||
|
fullwidth
|
||||||
|
disabled={!doesCustomerHaveNotValidatedDocuments}>
|
||||||
|
Relancer par mail
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className={classes["reminder-info"]}>
|
||||||
|
<Link
|
||||||
|
title={"Voir l'historique des relances"}
|
||||||
|
href={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.DocumentsReminderHistory.props.path.replace("[folderUid]", folderUid as string)
|
||||||
|
.replace("[customerUid]", customer.uid ?? "")}>
|
||||||
|
<IconButton icon={<ClockIcon />} variant={EIconButtonVariant.NEUTRAL} />
|
||||||
|
</Link>
|
||||||
|
<div className={classes["info"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Dernière relance:{" "}
|
||||||
|
{reminders && remindersLength > 0 ? new Date(reminders[0]!.reminder_date!).toLocaleDateString() : "-"}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Nombre de relance: {remindersLength}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ReminderModal isOpen={isOpen} onRemindSuccess={fetchReminders} onClose={close} customer={customer} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
@import "@Themes/constants.scss";
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
$mobile-breakpoint: 664px;
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -14,17 +16,46 @@
|
|||||||
.tabs {
|
.tabs {
|
||||||
width: calc(100% - 210px);
|
width: calc(100% - 210px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
border-bottom: 1px solid var(--tabs-stroke);
|
||||||
|
|
||||||
|
> :first-child {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> :last-child {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-lg, 24px);
|
gap: var(--spacing-lg, 24px);
|
||||||
|
|
||||||
.client-box {
|
@media screen and (max-width: $screen-s) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-box-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
gap: var(--spacing-lg, 24px);
|
gap: var(--spacing-lg, 24px);
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md, 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
flex-direction: column;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
|
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||||
|
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
import Tabs from "@Front/Components/Elements/Tabs";
|
import Tabs from "@Front/Components/Elements/Tabs";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline";
|
||||||
import Customer from "le-coffre-resources/dist/Customer";
|
import Customer from "le-coffre-resources/dist/Customer";
|
||||||
|
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||||
|
import Link from "next/link";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { AnchorStatus } from "..";
|
import { AnchorStatus } from "..";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import ClientBox from "./ClientBox";
|
import ClientBox from "./ClientBox";
|
||||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
|
||||||
import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline";
|
|
||||||
import Module from "@Front/Config/Module";
|
|
||||||
import Link from "next/link";
|
|
||||||
import NoDocument from "./NoDocument";
|
|
||||||
import DocumentTables from "./DocumentTables";
|
import DocumentTables from "./DocumentTables";
|
||||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
import EmailReminder from "./EmailReminder";
|
||||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
folder: OfficeFolder;
|
folder: OfficeFolder;
|
||||||
@ -57,8 +56,6 @@ export default function ClientView(props: IProps) {
|
|||||||
[customers],
|
[customers],
|
||||||
);
|
);
|
||||||
|
|
||||||
const doesCustomerHaveDocument = useMemo(() => customer.documents && customer.documents.length > 0, [customer]);
|
|
||||||
|
|
||||||
const handleClientDelete = useCallback(
|
const handleClientDelete = useCallback(
|
||||||
(customerUid: string) => {
|
(customerUid: string) => {
|
||||||
if (!folder.uid) return;
|
if (!folder.uid) return;
|
||||||
@ -95,7 +92,7 @@ export default function ClientView(props: IProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={classes["content"]}>
|
<div className={classes["content"]}>
|
||||||
<div className={classes["client-box"]}>
|
<div className={classes["client-box-container"]}>
|
||||||
<ClientBox
|
<ClientBox
|
||||||
customer={customer}
|
customer={customer}
|
||||||
anchorStatus={anchorStatus}
|
anchorStatus={anchorStatus}
|
||||||
@ -103,6 +100,7 @@ export default function ClientView(props: IProps) {
|
|||||||
onDelete={handleClientDelete}
|
onDelete={handleClientDelete}
|
||||||
customerNote={folder.notes!.find((value) => value.customer?.uid === customer.uid) ?? null}
|
customerNote={folder.notes!.find((value) => value.customer?.uid === customer.uid) ?? null}
|
||||||
/>
|
/>
|
||||||
|
<div className={classes["button-container"]}>
|
||||||
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||||
<Link
|
<Link
|
||||||
href={Module.getInstance()
|
href={Module.getInstance()
|
||||||
@ -114,12 +112,11 @@ export default function ClientView(props: IProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
<EmailReminder customer={customer} isAnchored={anchorStatus !== AnchorStatus.NOT_ANCHORED} />
|
||||||
</div>
|
</div>
|
||||||
{doesCustomerHaveDocument ? (
|
</div>
|
||||||
<DocumentTables documents={customer.documents ?? []} folderUid={folder?.uid ?? ""} />
|
|
||||||
) : (
|
{customer.uid && folder.uid && <DocumentTables customerUid={customer.uid} folderUid={folder.uid} />}
|
||||||
<NoDocument />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -1,18 +1,41 @@
|
|||||||
@import "@Themes/constants.scss";
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
$mobile-breakpoint: 600px;
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-lg, 40px);
|
gap: var(--spacing-lg, 24px);
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.info-box1 {
|
.info-box1 {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-sm, 8px);
|
gap: var(--spacing-sm, 8px);
|
||||||
|
|
||||||
|
.folder-number-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg, 24px);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.open-date {
|
.open-date {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-sm, 8px);
|
gap: var(--spacing-sm, 8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
width: 54vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-box2 {
|
.info-box2 {
|
||||||
@ -22,15 +45,42 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.progress-container {
|
.progress-container {
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
gap: var(--spacing-lg, 24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon-container {
|
.icon-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-md, 8px);
|
gap: var(--spacing-md, 8px);
|
||||||
|
|
||||||
|
&.desktop {
|
||||||
|
display: flex;
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobile {
|
||||||
|
display: none;
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +88,31 @@
|
|||||||
.text {
|
.text {
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.desktop {
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ipad {
|
||||||
|
display: none;
|
||||||
|
@media screen and (max-width: $screen-s) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobile {
|
||||||
|
display: none;
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,5 +121,10 @@
|
|||||||
background-color: var(--separator-stroke-light);
|
background-color: var(--separator-stroke-light);
|
||||||
width: 1px;
|
width: 1px;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-breakpoint) {
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
||||||
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
|
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
|
||||||
|
import Menu from "@Front/Components/DesignSystem/Menu";
|
||||||
|
import { IItem } from "@Front/Components/DesignSystem/Menu/MenuItem";
|
||||||
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
|
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
import { ArchiveBoxIcon, EllipsisHorizontalIcon, PencilSquareIcon, UsersIcon } from "@heroicons/react/24/outline";
|
import { ArchiveBoxIcon, EllipsisHorizontalIcon, PaperAirplaneIcon, PencilSquareIcon, UsersIcon } from "@heroicons/react/24/outline";
|
||||||
|
import classNames from "classnames";
|
||||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||||
import { useCallback } from "react";
|
import Link from "next/link";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { AnchorStatus } from "..";
|
import { AnchorStatus } from "..";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import { IItem } from "@Front/Components/DesignSystem/Menu/MenuItem";
|
|
||||||
import Menu from "@Front/Components/DesignSystem/Menu";
|
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
folder: OfficeFolder | null;
|
folder: OfficeFolder | null;
|
||||||
@ -23,7 +25,37 @@ type IProps = {
|
|||||||
export default function InformationSection(props: IProps) {
|
export default function InformationSection(props: IProps) {
|
||||||
const { folder, progress, onArchive, anchorStatus, isArchived } = props;
|
const { folder, progress, onArchive, anchorStatus, isArchived } = props;
|
||||||
|
|
||||||
const getSubMenuElement = useCallback(() => {
|
const menuItemsDekstop = useMemo(() => {
|
||||||
|
let elements: IItem[] = [];
|
||||||
|
|
||||||
|
// Creating the three elements and adding them conditionnally
|
||||||
|
const modifyCollaboratorsElement = {
|
||||||
|
icon: <UsersIcon />,
|
||||||
|
text: "Modifier les collaborateurs",
|
||||||
|
link: Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", folder?.uid ?? ""),
|
||||||
|
hasSeparator: true,
|
||||||
|
};
|
||||||
|
const modifyInformationsElement = {
|
||||||
|
icon: <PencilSquareIcon />,
|
||||||
|
text: "Modifier les informations du dossier",
|
||||||
|
link: Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""),
|
||||||
|
hasSeparator: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the folder is not anchored, we can modify the collaborators and the informations
|
||||||
|
if (anchorStatus === AnchorStatus.NOT_ANCHORED) {
|
||||||
|
elements.push(modifyCollaboratorsElement);
|
||||||
|
elements.push(modifyInformationsElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}, [anchorStatus, folder?.uid]);
|
||||||
|
|
||||||
|
const menuItemsMobile = useMemo(() => {
|
||||||
let elements: IItem[] = [];
|
let elements: IItem[] = [];
|
||||||
|
|
||||||
// Creating the three elements and adding them conditionnally
|
// Creating the three elements and adding them conditionnally
|
||||||
@ -44,33 +76,44 @@ export default function InformationSection(props: IProps) {
|
|||||||
hasSeparator: true,
|
hasSeparator: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const archiveElement = {
|
|
||||||
icon: <ArchiveBoxIcon />,
|
|
||||||
text: "Archiver le dossier",
|
|
||||||
onClick: onArchive,
|
|
||||||
color: ETypoColor.ERROR_WEAK_CONTRAST,
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the folder is not anchored, we can modify the collaborators and the informations
|
// If the folder is not anchored, we can modify the collaborators and the informations
|
||||||
if (anchorStatus === AnchorStatus.NOT_ANCHORED) {
|
if (anchorStatus === AnchorStatus.NOT_ANCHORED) {
|
||||||
elements.push(modifyCollaboratorsElement);
|
elements.push(modifyCollaboratorsElement);
|
||||||
// Remove the separator if it's the last item (if the folder is not archived)
|
|
||||||
if (isArchived) modifyInformationsElement.hasSeparator = false;
|
|
||||||
|
|
||||||
elements.push(modifyInformationsElement);
|
elements.push(modifyInformationsElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the folder is not archived, we can archive it
|
elements.push({
|
||||||
|
icon: <PaperAirplaneIcon />,
|
||||||
|
text: "Envoyer des documents",
|
||||||
|
link: Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.SendDocuments.props.path.replace("[folderUid]", folder?.uid ?? ""),
|
||||||
|
hasSeparator: !isArchived,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isArchived) {
|
if (!isArchived) {
|
||||||
elements.push(archiveElement);
|
elements.push({
|
||||||
|
icon: <ArchiveBoxIcon />,
|
||||||
|
text: "Archiver le dossier",
|
||||||
|
onClick: onArchive,
|
||||||
|
hasSeparator: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements;
|
return elements;
|
||||||
}, [anchorStatus, folder?.uid, isArchived, onArchive]);
|
}, [anchorStatus, folder?.uid, isArchived, onArchive]);
|
||||||
return (
|
return (
|
||||||
<section className={classes["root"]}>
|
<section className={classes["root"]}>
|
||||||
<div className={classes["info-box1"]}>
|
<div className={classes["info-box1"]}>
|
||||||
<div>
|
<div>
|
||||||
|
<div className={classes["folder-number-container"]}>
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR}>{folder?.folder_number}</Typography>
|
<Typography typo={ETypo.TEXT_MD_REGULAR}>{folder?.folder_number}</Typography>
|
||||||
|
<div className={classNames(classes["icon-container"], classes["mobile"])}>
|
||||||
|
<Menu items={menuItemsMobile}>
|
||||||
|
<IconButton icon={<EllipsisHorizontalIcon />} variant={EIconButtonVariant.NEUTRAL} />
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Typography typo={ETypo.TITLE_H4}>{folder?.name}</Typography>
|
<Typography typo={ETypo.TITLE_H4}>{folder?.name}</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -81,19 +124,37 @@ export default function InformationSection(props: IProps) {
|
|||||||
{folder?.created_at ? new Date(folder.created_at).toLocaleDateString() : ""}
|
{folder?.created_at ? new Date(folder.created_at).toLocaleDateString() : ""}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={classNames(classes["description-container"], classes["ipad"])}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||||
|
Note du dossier
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_REGULAR} className={classes["text"]}>
|
||||||
|
{folder?.description}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes["separator"]} />
|
<div className={classes["separator"]} />
|
||||||
|
|
||||||
<div className={classes["info-box2"]}>
|
<div className={classes["info-box2"]}>
|
||||||
<div className={classes["progress-container"]}>
|
<div className={classes["progress-container"]}>
|
||||||
<CircleProgress percentage={progress} />
|
<CircleProgress percentage={progress} />
|
||||||
<div className={classes["icon-container"]}>
|
<div className={classNames(classes["icon-container"], classes["desktop"])}>
|
||||||
<Menu items={getSubMenuElement()}>
|
<Link
|
||||||
|
href={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.SendDocuments.props.path.replace("[folderUid]", folder?.uid ?? "")}
|
||||||
|
title="Envoyer des documents">
|
||||||
|
<IconButton icon={<PaperAirplaneIcon />} variant={EIconButtonVariant.NEUTRAL} />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Menu items={menuItemsDekstop}>
|
||||||
<IconButton icon={<EllipsisHorizontalIcon />} variant={EIconButtonVariant.NEUTRAL} />
|
<IconButton icon={<EllipsisHorizontalIcon />} variant={EIconButtonVariant.NEUTRAL} />
|
||||||
</Menu>
|
</Menu>
|
||||||
|
{!isArchived && <IconButton onClick={onArchive} icon={<ArchiveBoxIcon />} variant={EIconButtonVariant.ERROR} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes["description-container"]}>
|
<div className={classNames(classes["description-container"], classes["desktop"], classes["mobile"])}>
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||||
Note du dossier
|
Note du dossier
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
margin: 24px auto;
|
||||||
|
width: 566px;
|
||||||
|
|
||||||
|
@media (max-width: $screen-m) {
|
||||||
|
width: 474px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $screen-s) {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-md, 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radioboxes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Radius-lg, 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
.buttons-container {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
212
src/front/Components/Layouts/Folder/SendDocuments/index.tsx
Normal file
212
src/front/Components/Layouts/Folder/SendDocuments/index.tsx
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import backgroundImage from "@Assets/images/background_refonte.svg";
|
||||||
|
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||||
|
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||||
|
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
|
import DragAndDrop from "@Front/Components/DesignSystem/DragAndDrop";
|
||||||
|
import Form from "@Front/Components/DesignSystem/Form";
|
||||||
|
import AutocompleteMultiSelectField from "@Front/Components/DesignSystem/Form/AutocompleteMultiSelectField";
|
||||||
|
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
||||||
|
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||||
|
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import { PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { ValidationError } from "class-validator";
|
||||||
|
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
enum EClientSelection {
|
||||||
|
ALL_CLIENTS = "all_clients",
|
||||||
|
SELECTED_CLIENTS = "selected_clients",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SendDocuments() {
|
||||||
|
const router = useRouter();
|
||||||
|
let { folderUid } = router.query;
|
||||||
|
const [folder, setFolder] = useState<OfficeFolder | null>(null);
|
||||||
|
const [clientSelection, setClientSelection] = useState<EClientSelection | null>(null);
|
||||||
|
const [selectedClients, setSelectedClients] = useState<string[]>([]);
|
||||||
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
|
const [isSending, setIsSending] = useState(false);
|
||||||
|
const [validationError, setValidationError] = useState<ValidationError | null>(null);
|
||||||
|
|
||||||
|
const onFormSubmit = useCallback(
|
||||||
|
async (
|
||||||
|
_e: React.FormEvent<HTMLFormElement> | null,
|
||||||
|
_values: {
|
||||||
|
[key: string]: any;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
if (!files.length) {
|
||||||
|
console.error("No files to send");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsSending(true);
|
||||||
|
|
||||||
|
if (selectedClients.length === 0) {
|
||||||
|
setValidationError({
|
||||||
|
property: "clients",
|
||||||
|
constraints: {
|
||||||
|
isEmpty: "Veuillez sélectionner au moins un client",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
throw new Error("No clients selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
selectedClients.map(async (customer) => {
|
||||||
|
const promises = files.map(async (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("customerUid", customer as string);
|
||||||
|
formData.append("folderUid", folderUid as string);
|
||||||
|
formData.append("name", file.name);
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
|
// Envoi de chaque fichier pour chaque client
|
||||||
|
return DocumentsNotary.getInstance().post(formData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attendre que tous les fichiers pour un client soient envoyés
|
||||||
|
await Promise.all(promises);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
router.push(
|
||||||
|
Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
|
||||||
|
);
|
||||||
|
setIsSending(false);
|
||||||
|
ToasterService.getInstance().success({ title: "Succès !", description: "Votre document a été envoyée avec succès." });
|
||||||
|
} catch (error) {
|
||||||
|
setIsSending(false);
|
||||||
|
console.warn("Error while sending files: ", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[files, folderUid, router, selectedClients],
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchFolder = useCallback(async () => {
|
||||||
|
Folders.getInstance()
|
||||||
|
.getByUid(folderUid as string, {
|
||||||
|
q: {
|
||||||
|
customers: {
|
||||||
|
include: {
|
||||||
|
contact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((folder) => setFolder(folder))
|
||||||
|
.catch((e) => console.warn(e));
|
||||||
|
}, [folderUid]);
|
||||||
|
|
||||||
|
const onClientSelectionChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const selection = e.target.value as EClientSelection;
|
||||||
|
setClientSelection(selection);
|
||||||
|
|
||||||
|
if (selection === EClientSelection.ALL_CLIENTS && folder?.customers) {
|
||||||
|
const allClientIds = folder.customers.map((customer) => customer.uid ?? "");
|
||||||
|
setSelectedClients(allClientIds);
|
||||||
|
} else {
|
||||||
|
setSelectedClients([]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[folder],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClientSelectionChange = useCallback((selectedOptions: any) => {
|
||||||
|
setSelectedClients(selectedOptions.map((option: any) => option.id));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFolder();
|
||||||
|
}, [fetchFolder]);
|
||||||
|
|
||||||
|
const backUrl = useMemo(
|
||||||
|
() =>
|
||||||
|
Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
|
||||||
|
[folderUid],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFileChange = useCallback((files: File[]) => {
|
||||||
|
setFiles(files);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clientsOptions = useMemo(() => {
|
||||||
|
if (!folder?.customers) return [];
|
||||||
|
return folder.customers.map((customer) => ({
|
||||||
|
id: customer.uid ?? "",
|
||||||
|
label: `${customer.contact?.first_name} ${customer.contact?.last_name}`,
|
||||||
|
}));
|
||||||
|
}, [folder]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaultDoubleSidePage title={"Demander des documents"} image={backgroundImage} showHeader>
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<BackArrow url={backUrl} />
|
||||||
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
Envoyer des documents, sélectionnez les destinataires :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Voulez-vous envoyer ce document à tous les clients du dossier ou sélectionner certains clients ?{" "}
|
||||||
|
</Typography>
|
||||||
|
<div className={classes["radioboxes"]}>
|
||||||
|
<RadioBox
|
||||||
|
name="clients"
|
||||||
|
value={EClientSelection.ALL_CLIENTS}
|
||||||
|
label="Sélectionner tous les clients du dossier"
|
||||||
|
onChange={onClientSelectionChange}
|
||||||
|
/>
|
||||||
|
<RadioBox
|
||||||
|
name="clients"
|
||||||
|
value={EClientSelection.SELECTED_CLIENTS}
|
||||||
|
label="Sélectionner certains clients"
|
||||||
|
onChange={onClientSelectionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form onSubmit={onFormSubmit} className={classes["form"]}>
|
||||||
|
{clientSelection === EClientSelection.SELECTED_CLIENTS && (
|
||||||
|
<AutocompleteMultiSelectField
|
||||||
|
name="clients"
|
||||||
|
label="Choisir le ou les clients: "
|
||||||
|
options={clientsOptions}
|
||||||
|
onSelectionChange={handleClientSelectionChange}
|
||||||
|
validationError={validationError ?? undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{clientSelection && (
|
||||||
|
<>
|
||||||
|
<DragAndDrop
|
||||||
|
name="files"
|
||||||
|
title="Glisser ou déposer ou"
|
||||||
|
description="Formats acceptés : PDF, JPG Taille maximale : 5 Mo"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={classes["buttons-container"]}>
|
||||||
|
<a href={backUrl}>
|
||||||
|
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
|
||||||
|
Annuler
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
<Button type="submit" rightIcon={<PaperAirplaneIcon />} isLoading={isSending}>
|
||||||
|
Envoyer
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</DefaultDoubleSidePage>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
import Form from "@Front/Components/DesignSystem/Form";
|
import Form from "@Front/Components/DesignSystem/Form";
|
||||||
import Select, { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
import Select, { IOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
||||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||||
import BackArrow from "@Front/Components/Elements/BackArrow";
|
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||||
@ -81,7 +81,7 @@ export default function UpdateFolderMetadata() {
|
|||||||
const deedOption = {
|
const deedOption = {
|
||||||
label: selectedFolder?.deed?.deed_type?.name,
|
label: selectedFolder?.deed?.deed_type?.name,
|
||||||
value: selectedFolder?.deed?.deed_type?.uid,
|
value: selectedFolder?.deed?.deed_type?.uid,
|
||||||
} as IOptionOld;
|
} as IOption;
|
||||||
const openingDate = new Date(selectedFolder?.created_at ?? "");
|
const openingDate = new Date(selectedFolder?.created_at ?? "");
|
||||||
if (!selectedFolder?.created_at) return <></>;
|
if (!selectedFolder?.created_at) return <></>;
|
||||||
const defaultValue = openingDate.toISOString().split("T")[0];
|
const defaultValue = openingDate.toISOString().split("T")[0];
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
.root {
|
.root {
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 648px;
|
max-width: 648px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--spacing-2xl, 40px);
|
gap: var(--spacing-2xl, 40px);
|
||||||
@ -14,8 +14,8 @@
|
|||||||
gap: var(--spacing-md, 16px);
|
gap: var(--spacing-md, 16px);
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
|
||||||
.logo{
|
.logo {
|
||||||
fill: "red"
|
fill: "red";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,17 +39,23 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-lg, 24px);
|
gap: var(--spacing-lg, 24px);
|
||||||
|
|
||||||
.box {
|
@media screen and (max-width: 600px) {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-sm, 8px);
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.mobile {
|
||||||
background-color: var(--separator-stroke-light);
|
display: none;
|
||||||
width: 1px;
|
@media screen and (max-width: 600px) {
|
||||||
align-self: stretch;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desktop {
|
||||||
|
display: flex;
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,36 @@
|
|||||||
import LogoIcon from "@Assets/logo_small_blue.svg";
|
import LogoIcon from "@Assets/logo_small_blue.svg";
|
||||||
import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
|
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
|
import Separator, { ESeperatorColor, ESeperatorDirection } from "@Front/Components/DesignSystem/Separator";
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import HelpBox from "@Front/Components/Elements/HelpBox";
|
||||||
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
import useUser from "@Front/Hooks/useUser";
|
||||||
import { DocumentIcon } from "@heroicons/react/24/outline";
|
import { DocumentIcon } from "@heroicons/react/24/outline";
|
||||||
import User from "le-coffre-resources/dist/Notary";
|
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
|
||||||
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
export default function Folder() {
|
export default function Folder() {
|
||||||
const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true);
|
const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [activeUser, setActiveUser] = useState<User | null>();
|
const { user: activeUser } = useUser();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const decodedJwt = JwtService.getInstance().decodeJwt();
|
|
||||||
if (!decodedJwt) return;
|
|
||||||
Users.getInstance()
|
|
||||||
.getByUid(decodedJwt.userId, {
|
|
||||||
q: {
|
|
||||||
contact: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((user) => {
|
|
||||||
setActiveUser(user);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Folders.getInstance()
|
Folders.getInstance()
|
||||||
.get({
|
.get({
|
||||||
q: {
|
q: {
|
||||||
where: { status: EFolderStatus.LIVE },
|
where: { status: EFolderStatus.LIVE },
|
||||||
|
orderBy: { created_at: "desc" },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
console.log(folders);
|
|
||||||
if (folders.length > 0)
|
if (folders.length > 0)
|
||||||
router.push(
|
router.push(
|
||||||
Module.getInstance()
|
Module.getInstance()
|
||||||
@ -53,7 +39,6 @@ export default function Folder() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}>
|
<DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}>
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
@ -62,13 +47,13 @@ export default function Folder() {
|
|||||||
<Image src={LogoIcon} alt="logo" />
|
<Image src={LogoIcon} alt="logo" />
|
||||||
|
|
||||||
{activeUser && activeUser.contact && (
|
{activeUser && activeUser.contact && (
|
||||||
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.COLOR_PRIMARY_500}>
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_ACCENT}>
|
||||||
Bonjour {activeUser.contact.first_name}, bienvenue sur LeCoffre.io
|
Bonjour {activeUser.contact.first_name}, bienvenue sur LeCoffre.io
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{!activeUser ||
|
{!activeUser ||
|
||||||
(!activeUser.contact && (
|
(!activeUser.contact && (
|
||||||
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.COLOR_PRIMARY_500}>
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_ACCENT}>
|
||||||
Bonjour, bienvenue sur LeCoffre.io
|
Bonjour, bienvenue sur LeCoffre.io
|
||||||
</Typography>
|
</Typography>
|
||||||
))}
|
))}
|
||||||
@ -96,22 +81,25 @@ export default function Folder() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classes["help-container"]}>
|
<div className={classes["help-container"]}>
|
||||||
<div className={classes["box"]}>
|
<HelpBox
|
||||||
<Typography typo={ETypo.TEXT_MD_SEMIBOLD}>Besoin d'aide ?</Typography>
|
title="Besoin d'aide ?"
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR}>Consultez nos guides pour bien démarrer.</Typography>
|
description="Consultez nos guides pour bien démarrer."
|
||||||
<Button variant={EButtonVariant.SECONDARY} styletype={EButtonstyletype.TEXT} size={EButtonSize.MD}>
|
button={{ text: "Accéder aux guides" }}
|
||||||
Accéder aux guides
|
/>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className={classes["separator"]} />
|
|
||||||
|
|
||||||
<div className={classes["box"]}>
|
<Separator
|
||||||
<Typography typo={ETypo.TEXT_MD_SEMIBOLD}>Vous avez des questions ?</Typography>
|
className={classes["desktop"]}
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR}>Notre équipe de support est là pour vous aider.</Typography>
|
direction={ESeperatorDirection.VERTICAL}
|
||||||
<Button variant={EButtonVariant.SECONDARY} styletype={EButtonstyletype.TEXT} size={EButtonSize.MD}>
|
size={128}
|
||||||
Contactez le support
|
color={ESeperatorColor.LIGHT}
|
||||||
</Button>
|
/>
|
||||||
</div>
|
<Separator className={classes["mobile"]} direction={ESeperatorDirection.HORIZONTAL} color={ESeperatorColor.LIGHT} />
|
||||||
|
|
||||||
|
<HelpBox
|
||||||
|
title="Vous avez des questions ?"
|
||||||
|
description="Notre équipe de support est là pour vous aider."
|
||||||
|
button={{ text: "Contactez le support" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
import Form from "@Front/Components/DesignSystem/Form";
|
import Form from "@Front/Components/DesignSystem/Form";
|
||||||
import Select, { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
import Select, { IOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
||||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||||
import BackArrow from "@Front/Components/Elements/BackArrow";
|
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||||
@ -18,7 +18,7 @@ type IProps = {
|
|||||||
};
|
};
|
||||||
type IState = {
|
type IState = {
|
||||||
selectedFolder: OfficeFolder | null;
|
selectedFolder: OfficeFolder | null;
|
||||||
selectedOption?: IOptionOld;
|
selectedOption?: IOption;
|
||||||
};
|
};
|
||||||
class UpdateFolderMetadataClass extends BasePage<IProps, IState> {
|
class UpdateFolderMetadataClass extends BasePage<IProps, IState> {
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
@ -74,7 +74,7 @@ class UpdateFolderMetadataClass extends BasePage<IProps, IState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSelectedOption(option: IOptionOld) {
|
private onSelectedOption(option: IOption) {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedOption: option,
|
selectedOption: option,
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
.root {
|
||||||
|
width: 1056px;
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md, 16px);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,597 @@
|
|||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import Dot from "@Front/Components/Layouts/Legal/LegalInformations/elements/Dot";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
|
||||||
|
import LinkInline from "../elements/LinkInline";
|
||||||
|
|
||||||
|
export default function CGU() {
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
Conditions Générales d'Utilisation
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY} italic>
|
||||||
|
Version en vigueur à compter du 24 avril 2024
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les présentes Conditions Générales d’Utilisation sont conclues entre LEcoffre.io, 2 Mail Anne-Catherine – 35000 Rennes,
|
||||||
|
Numéro Siret 92747436100015, TVA intracommunautaire FR17927474361, et l’Internaute.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Tout Internaute qui souhaite utiliser le Site
|
||||||
|
<LinkInline text="https://www.lecoffre.io" href="https://www.lecoffre.io" />
|
||||||
|
(ci-après, le « Site ») dans sa version ordinateur ou mobile est réputé avoir pris connaissance des présentes conditions
|
||||||
|
générales d’utilisation (ci-après, les « CGU »).
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Toute utilisation du Site nécessite la consultation et l’acceptation des présentes conditions générales. L’Internaute
|
||||||
|
reconnaît que l’utilisation du Site nécessite le respect de l’ensemble des prescriptions définies au sein des présentes.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Il est tenu à chaque visite de prendre connaissance d’éventuelles évolutions de ces dernières, LEcoffre.io se réservant
|
||||||
|
le droit de les modifier à tout moment.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Ces conditions générales d’utilisation ne sont pas des conditions générales de vente ou des conditions générales de
|
||||||
|
prestations de services.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Elles ont vocation à constituer la règle que doit respecter tout Internaute se rendant sur le Site LEcoffre.io
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY} italic>
|
||||||
|
Dernière mise à jour le 24 avril 2024.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
1. Définitions
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les termes ci-dessous définis auront entre les Parties la signification suivante :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>« Internaute » : toute personne accédant et mettant en œuvre les fonctionnalités du Site ;</Dot>
|
||||||
|
<Dot>
|
||||||
|
« Parties » : désigne au singulier indistinctement Lecoffre.io ou l’Internaute, et au pluriel le LEcoffre.io et
|
||||||
|
l’Internaute ensemble ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
« Règlementation relative à la protection des données à caractère personnel » : ° la loi relative à l’informatique,
|
||||||
|
aux fichiers et aux libertés n° 78-17 du 6 janvier 1978 modifiée et ses mises à jour ou modification ; et
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
le Règlement (UE) 2016/679 du Parlement européen et du Conseil du 27 avril 2016 (règlement général sur la protection
|
||||||
|
des données) ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
« Site » : site Internet édité par LEcoffre.io accessible à
|
||||||
|
<LinkInline text="https://www.lecoffre.io" href="https://www.lecoffre.io" />
|
||||||
|
</Dot>
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
2. Description des services
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Le Site permet un accès au service LECoffre.io qui est une plateforme en ligne, par abonnement, permettant le transfert
|
||||||
|
sécurisé de données et documents à destination des notaires et de leurs offices.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Le fait de déposer un document sur la plateforme entraîne donc la conservation temporaire des documents transmis.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Une fois le dossier complété et l'ensemble des documents nécessaires à la préparation d'un acte notarié réalisé, le
|
||||||
|
notaire peut obtenir l'ensemble des documents après que la plateforme lecoffre.io ait certifié l'origine du document via
|
||||||
|
l'identification de son émetteur et verrouillé l'état des documents par l'établissement d'un jeton unique
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
La technologie utilisée repose sur l'utilisation d’une technologie blockchain.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute est invité à se référer aux
|
||||||
|
<LinkInline
|
||||||
|
text="Conditions Générales de Service"
|
||||||
|
href={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Legal.pages.LegalInformations.props.path.replace("[legalUid]", ELegalOptions.CGS)}
|
||||||
|
/>
|
||||||
|
concernant l’utilisation de la plateforme.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
3. Entrée en vigueur – Durée
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les présentes conditions générales d’utilisation entrent en vigueur à la date de leur mise en ligne et seront opposables
|
||||||
|
à la date de la première utilisation du Site par l’Internaute.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les présentes conditions d’utilisation sont opposables pendant toute la durée d’utilisation du Site et jusqu’à ce que de
|
||||||
|
nouvelles conditions générales d’utilisation remplacent les présentes.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute peut à tout moment renoncer à utiliser le Site mais reste responsable de toute utilisation antérieure.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
4. Objet des CGU
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les présentes CGU ont pour objet de présenter et de fixer les conditions d’utilisation du Site, et l’ensemble des pages
|
||||||
|
du Site.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute déclare avoir pris connaissance et accepté les présentes conditions générales d’utilisation
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
5. Conditions d'accès
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’accès au Site est libre et gratuit pour tout Internaute disposant d'un accès à internet ou à un réseau mobile. Tous
|
||||||
|
les coûts afférents à l'accès, que ce soit les frais matériels, logiciels ou d'accès à internet sont exclusivement à la
|
||||||
|
charge de l'Utilisateur. Il est seul responsable du bon fonctionnement de son équipement mobile ainsi que de son accès à
|
||||||
|
internet.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io s’efforce de fournir un accès de qualité et de permettre aux Internautes d'utiliser les moyens de
|
||||||
|
communication mis à leur disposition dans les meilleures conditions possibles.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En raison de la nature et de la complexité du réseau de l’Internet, et en particulier, de ses performances techniques et
|
||||||
|
des temps de réponse pour consulter, interroger ou transférer les données d’informations, LEcoffre.io fait ses meilleurs
|
||||||
|
efforts, conformément aux règles de l’art, pour permettre l’accès et l’utilisation du Site. LEcoffre.io ne saurait en
|
||||||
|
effet assurer une accessibilité ou une disponibilité absolue du Site.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Toutefois, LEcoffre.io se réserve le droit, sans préavis, ni indemnité, de fermer temporairement ou définitivement tout
|
||||||
|
ou partie du service notamment pour effectuer une mise à jour, des opérations de maintenance, des modifications ou
|
||||||
|
changements sur les méthodes opérationnelles et les serveurs, sans que cette liste ne soit limitative.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io n'est pas responsable des dommages de toute nature qui peuvent résulter de ces changements ou d'une
|
||||||
|
indisponibilité temporaire ou encore de la fermeture définitive de tout ou partie du Site ou des services qui y sont
|
||||||
|
associés.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io se réserve le droit de compléter ou de modifier, à tout moment, le Site et les services qui y sont
|
||||||
|
disponibles en fonction de l’évolution des technologies.{" "}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Il appartient à l’Internaute de veiller aux possibilités d’évolution des moyens informatiques et de transmission à sa
|
||||||
|
disposition pour que ces moyens puissent s’adapter aux évolutions du service.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
6. Liens hypertextes
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io se réserve la possibilité de mettre en place des hyperliens sur son Site, donnant accès à des pages web
|
||||||
|
autres que celles de son Site sur lesquels LEcoffre.io n’exerce aucun contrôle.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io décline toute responsabilité quant aux conditions d’accès à ces pages web, à leur fonctionnement, à leur
|
||||||
|
utilisation des données à caractère personnel des Internautes et quant au contenu des informations fournies sur ces
|
||||||
|
pages web au titre de l’activation des hyperliens.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L'Internaute qui dispose d’un site internet à titre personnel et désire placer, à des fins personnelles, sur son site un
|
||||||
|
lien simple renvoyant directement sur la page d’accueil du Site du LEcoffre.io, doit obtenir l’autorisation expresse de
|
||||||
|
LEcoffre.io pour établir ce lien.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En aucun cas, cette autorisation ne pourra être qualifiée de convention implicite d’affiliation.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En toute hypothèse, les liens hypertextes renvoyant au Site sans l’autorisation de LEcoffre.io devront être retirés à
|
||||||
|
première demande de LEcoffre.io.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
7. Sécurité
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io fait ses meilleurs efforts, conformément aux règles de l’art, pour sécuriser le Site au regard du risque
|
||||||
|
encouru et de la nature des données traitées. Toutefois, LEcoffre.io ne saurait être responsable, s’agissant de ce
|
||||||
|
point, qu’en cas de faute prouvée imputable à cette dernière. Il ne saurait assurer une sécurité absolue.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Il est interdit à l’Internaute d'accéder ou de se maintenir, frauduleusement, dans tout ou partie du Site. Il s'interdit
|
||||||
|
d’utiliser une méthode d’accès autre que l’interface mise à disposition par LEcoffre.io. En cas de découverte d'une
|
||||||
|
telle méthode ou si l’Internaute entre dans un espace réservé, sans droit, par inadvertance, celui-ci s'engage à en
|
||||||
|
informer sans délai LEcoffre.io par courrier électronique à l’adresse (contact@lecoffre.io ) afin qu’il puisse prendre
|
||||||
|
les mesures nécessaires.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Il est interdit à l’Internaute de supprimer ou modifier des données contenues sur le Site qui n’auraient pas été
|
||||||
|
publiées par lui-même, ou d’y introduire frauduleusement des données ou même d’opérer une altération du fonctionnement
|
||||||
|
du Site. Il veille notamment à ne pas introduire de virus, code malveillant ou toute autre technologie nuisible au Site
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Tout accès à un espace interdit sera considéré comme un accès frauduleux au sens des dispositions du Code pénal.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute s’engage à considérer que toutes les données dont il aura eu connaissance à l’occasion d’un tel accès à un
|
||||||
|
espace non autorisé sont des données confidentielles et s’engage, en conséquence, à ne pas les divulguer.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute s’interdit notamment de réaliser toute opération visant à saturer une page, les opérations de rebond ou
|
||||||
|
toute opération ayant pour conséquence d'entraver ou de fausser le fonctionnement du Site
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute s’engage à ne pas utiliser de dispositifs ou de logiciels de toutes sortes qui auraient pour conséquence de
|
||||||
|
perturber le bon fonctionnement du Site.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute s’engage à ne pas engager d'action qui imposerait une charge disproportionnée sur les infrastructures du
|
||||||
|
Site.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute accepte les caractéristiques et limites de l’internet. Il a conscience que les données circulant sur
|
||||||
|
l’internet ne sont pas nécessairement protégées, notamment contre les détournements éventuels.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute prend les mesures appropriées pour assurer la sécurité de ses propres données et / ou logiciels de la
|
||||||
|
contamination par des éventuels virus sur le réseau internet.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
8. Données à caractère personnel
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io déclare qu’il traite les données à caractère personnel conformément aux dispositions de la loi n°78-17 du 6
|
||||||
|
janvier 1978 modifiée et du Règlement (UE) 2016/679 du 27 avril 2016 (RGPD).
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute est invité à se référer aux
|
||||||
|
<LinkInline
|
||||||
|
text="Politique de confidentialité"
|
||||||
|
href={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Legal.pages.LegalInformations.props.path.replace(
|
||||||
|
"[legalUid]",
|
||||||
|
ELegalOptions.POLITIQUE_DE_CONFIDENTIALITE,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
concernant l’utilisation par LECoffre.io des données à caractère personnel.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute est invité à se référer aux
|
||||||
|
<LinkInline
|
||||||
|
text=" Politiques de Cookies"
|
||||||
|
href={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Legal.pages.LegalInformations.props.path.replace(
|
||||||
|
"[legalUid]",
|
||||||
|
ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
concernant l’utilisation par LECoffre.io des cookies.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
9. Responsabilité de LEcoffre.io
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
9.1. Responsabilité générale
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
La responsabilité de LECoffre.io ne pourra pas être retenue en cas de non-compatibilité du système d’information de
|
||||||
|
l’Office avec les Services proposés.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Pour des raisons de maintenance, ou du fait de tiers, l’accès et le fonctionnement, partiel ou total des Services
|
||||||
|
LECoffre.io, pourront être suspendus, sans que cette interruption puisse ouvrir droit à une quelconque indemnité au
|
||||||
|
bénéfice de l’Office ou de tout tiers ou constituer une cause de résiliation.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En cas d’interruption des Services LECoffre.io, l’éditeur mettra tous les moyens nécessaires en œuvre pour rétablir le
|
||||||
|
plus rapidement possible l’accès aux services, sauf si l’interruption résulte d’un défaut qui ne lui est pas imputable.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
La responsabilité de LECoffre.io ne pourra être engagée sur le fondement d’une garantie de délai de traitement des
|
||||||
|
échanges dématérialisés.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LECoffre.io est un prestataire technique mettant en œuvre un processus d’échanges et d’archivage dématérialisés de
|
||||||
|
documents et de données.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Dans le cas de traitement d’anomalies techniques, LECoffre.io peut être amenée à analyser les aspects techniques
|
||||||
|
contenus dans les flux à l’exception de tout autre aspect tenant notamment à la validité de l’opération réalisée, aux
|
||||||
|
effets juridiques liés à l’opération, etc.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Dans l’hypothèse où la responsabilité de LECoffre.io se trouverait engagée, pour quelque cause que ce soit, au titre des
|
||||||
|
présentes conditions, la responsabilité de LECoffre.io est expressément limitée la sécurisation des données qui lui ont
|
||||||
|
été transmises par les utilisateurs, professionnels ou non, à l’exclusion de tous dommages indirects (notamment dommages
|
||||||
|
commerciaux et moraux, perte de chiffre d’affaires, perte de clientèle) qui seront exclus de la réparation due par
|
||||||
|
LECoffre.io.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
9.2. Responsabilité à l’égard des informations présentes sur le Site
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io fait ses meilleurs efforts pour proposer sur son Site des informations à jour.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Toutes les informations portées à la connaissance des personnes accédant au Site ont été sélectionnées à une date
|
||||||
|
déterminée. Compte tenu de la nature et de la complexité des technologies qui sont mises en œuvre, chaque Partie
|
||||||
|
reconnaît que le Site ne peut être exempte d’Anomalies, de défaillances, d’interruptions et d’indisponibilités.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En cas de dysfonctionnement ou d’incident de quelque nature que ce soit lors de l’utilisation du Site, l’Internaute doit
|
||||||
|
en informer immédiatement LEcoffre.io.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io ne saurait être tenue responsable de toute erreur ou omission.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Toute mise à jour, nouvelle prestation ou nouvelle caractéristique qui améliore ou augmente un ou plusieurs contenus
|
||||||
|
d’informations existants sera soumis aux présentes conditions.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les équipements (notamment ordinateur, téléphone, logiciels, moyens de communication électronique) permettant l’accès et
|
||||||
|
l’utilisation au Site sont à la charge exclusive des Internautes, de même que les frais de communications électroniques
|
||||||
|
(notamment coûts téléphoniques, coûts d’accès à Internet) résultant de leur utilisation.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
10. Responsabilités de l’Internaute
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Dans le cadre des présentes CGU, l‘Internaute a été informé qu’il devait s’assurer :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>du respect des préconisations techniques requises ;</Dot>
|
||||||
|
<Dot>
|
||||||
|
qu’il dispose de la compétence nécessaire et des informations suffisantes pour une utilisation optimale du Site.
|
||||||
|
</Dot>
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute demeure intégralement et exclusivement responsable de l’exploitation des informations obtenues sur le Site.{" "}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
De manière générale, chaque Internaute s’engage à utiliser le Site :{" "}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>dans le respect des lois, réglementation et droits des tiers ;</Dot>
|
||||||
|
<Dot>
|
||||||
|
de manière loyale et conformément à sa destination, dans le respect des présentes CGU et de la Réglementation.
|
||||||
|
</Dot>
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
11. Obligations de l’Internaute
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute s’engage à n’utiliser le Site ainsi que l’ensemble des informations auxquelles il pourra avoir accès que
|
||||||
|
pour obtenir des informations et dans un but conforme à l’ordre public, aux bonnes mœurs et aux droits des tiers.{" "}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Ainsi, il lui est interdit de publier ou de transmettre via le Site tout élément illicite, préjudiciable, diffamatoire,
|
||||||
|
pornographique, haineux, raciste ou autrement attentatoire à la dignité humaine notamment. Au cas où LEcoffre.io serait
|
||||||
|
informé de la présence de contenus illicites sur le Site, LEcoffre.io serait en droit d'en supprimer immédiatement le
|
||||||
|
contenu et empêcher l’accès au Site à l’Internaute.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Internaute s’engage, par ailleurs, à ne pas perturber l’usage que pourraient faire les autres Internautes du Site de
|
||||||
|
ne pas accéder à des Parties du Site dont l’accès est réservé.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
12. Propriété intellectuelle
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
12.1 Propriété intellectuelle de LEcoffre.io
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Le contenu du Site, la structure générale ainsi que les logiciels, textes, images animées ou non, photographies, son
|
||||||
|
savoir-faire et tous les autres éléments composant le Site sont la propriété exclusive de LEcoffre.io ou des tiers qui
|
||||||
|
lui ont concédé une licence.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’ensemble des éléments du le Site, sont protégés par le droit d’auteur, le droit des marques, des dessins et modèles
|
||||||
|
et/ou tous autres droits de propriété intellectuelle.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Aucun titre ni droit quelconque sur aucun élément ou logiciel ne sera obtenu par téléchargement ou copie d’éléments de
|
||||||
|
ce Site. Il est formellement interdit à l’Internaute de reproduire (à part pour son utilisation personnelle et non
|
||||||
|
commerciale), publier, 7 éditer, transmettre, distribuer, montrer, enlever, supprimer, ajouter à ce Site et aux éléments
|
||||||
|
et logiciels qu’ils contiennent, pas plus que les modifier ou effectuer un quelconque travail en les prenant pour base,
|
||||||
|
ni vendre ou participer à aucune vente en rapport avec ce Site, les éléments de ce Site ni aucun logiciel y afférant.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L'Internaute s'interdit notamment de modifier, copier, reproduire, télécharger, diffuser, transmettre, exploiter
|
||||||
|
commercialement et/ou distribuer de quelque façon que ce soit les pages du Site, ou les codes informatiques des éléments
|
||||||
|
composant le Site.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les présentes conditions générales d’utilisation n’emportent aucune cession d’aucune sorte de droit de propriété
|
||||||
|
intellectuelle sur les éléments appartenant à LEcoffre.io ou ayants droit tels que les sons, photographies, images,
|
||||||
|
textes littéraires, travaux artistiques, logiciels, marques, chartes graphiques, logos au bénéfice du consommateur.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io concède à l’Internaute une licence non exclusive pour utiliser le Site. Cette licence est strictement
|
||||||
|
personnelle et ne peut en aucun cas être cédée ou transférée à quel que tiers que ce soit. La licence est concédée pour
|
||||||
|
la durée d’utilisation du Site.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Toute reproduction et/ou représentation, totale ou partielle d’un de ces droits, sans l’autorisation expresse de
|
||||||
|
LEcoffre.io, est interdite et constituerait une contrefaçon susceptible d'engager la responsabilité civile et pénale du
|
||||||
|
contrefacteur.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En conséquence, l'Internaute s'interdit tout agissement et tout acte susceptible de porter atteinte directement ou non
|
||||||
|
aux droits de propriété intellectuelle de LEcoffre.io.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Il en est de même des bases de données figurant, le cas échéant, sur le Site qui sont protégées par les articles du Code
|
||||||
|
de la propriété intellectuelle. Toute extraction ou réutilisation, totale ou partielle desdites bases et de leur contenu
|
||||||
|
est ainsi interdite sans l’autorisation préalable expresse de LEcoffre.io.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Seule une utilisation conforme à la destination de ce Site est autorisée.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Toute autre utilisation, non expressément autorisée par écrit et au préalable par LEcoffre.io, est prohibée et
|
||||||
|
constitutive de contrefaçon.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
12.2 Propriété intellectuelle des tiers
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les éléments appartenant à des tiers, tels qu’extrait de films, marques, logo, images, textes, sons, sans que cette
|
||||||
|
liste ne soit exhaustive, sont la propriété exclusive de leur auteur et sont protégés à ce titre par le droit d’auteur,
|
||||||
|
le droit des marques ou tout autre droit reconnu par les lois en vigueur.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L'Utilisateur s'interdit de porter atteinte, directement ou indirectement, au droit de propriété de tiers, dont les
|
||||||
|
contenus sont présents sur le Site et s'interdit d'exploiter ces éléments de quelque manière que ce soit.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L'Utilisateur s'engage à respecter l'intégralité des droits de tiers, dont les contenus sont présents sur le Site.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
13. Tolérance
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’Utilisateur convient que le fait pour LEcoffre.io de tolérer une situation n’a pas pour effet d’accorder à
|
||||||
|
l’utilisateur des droits acquis.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
De plus, une telle tolérance ne peut être interprétée comme une renonciation à faire valoir les droits en cause.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
14. Titres
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En cas de difficultés d’interprétation résultant d’une contradiction entre l’un quelconque des titres figurant en tête
|
||||||
|
des clauses et l’une quelconque des clauses, les titres seront déclarés inexistants.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
15. Nullité
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Si une ou plusieurs stipulations des présentes conditions générales d’utilisation sont tenues pour non valides ou
|
||||||
|
déclarées comme telles en application d’une loi, d’un règlement ou à la suite d’une décision passée en force de chose
|
||||||
|
jugée d’une juridiction compétente, les autres stipulations garderont toute leur force et leur portée.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
16. Domiciliation
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Pour l’exécution de la présente convention et sauf dispositions particulières, l’Internaute convient d’adresser toute
|
||||||
|
correspondance à l’adresse postale de LEcoffre.io : 2, Mail Anne Catherine, 35000 Rennes
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
17. Langues et compétences
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les présentes Conditions générales d’utilisation ont été initialement rédigées en français.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
La version française prévaudra sur toute autre traduction en cas de contestation, litige, difficulté d'interprétation ou
|
||||||
|
d'exécution des présentes Conditions.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
18. Litige
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Compte tenu de la dimension mondiale du réseau Internet, nous vous informons qu'il faut vous conformer à toutes les
|
||||||
|
règles applicables dans le pays dans lequel vous résidez.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les présentes conditions générales d’utilisation du Site sont régies par la loi française.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En cas de litige lié à l’interprétation, la validité et les conséquences des présentes CGU et, à défaut de solution
|
||||||
|
amiable préalable, les tribunaux de Rennes seront seuls compétents.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Il en est ainsi pour les règles de fond et les règles de forme et ce, nonobstant les lieux d’exécution des obligations
|
||||||
|
substantielles ou accessoires.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY} italic>
|
||||||
|
QUESTIONS ? PROBLÈMES ? SUGGESTIONS ?
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Nous vous prions de nous contacter à : (contact@lecoffre.io) ou par courrier à LEcoffre.io, 2, Mail Anne-Catherine –
|
||||||
|
35000 Rennes, France, afin de signaler toute violation des conditions générales d’utilisation ou pour poser toute
|
||||||
|
question concernant les CGU,
|
||||||
|
<LinkInline
|
||||||
|
text="Politique de confidentialité"
|
||||||
|
href={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Legal.pages.LegalInformations.props.path.replace(
|
||||||
|
"[legalUid]",
|
||||||
|
ELegalOptions.POLITIQUE_DE_CONFIDENTIALITE,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
et/ou le Site.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
.root {
|
||||||
|
width: 1056px;
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md, 16px);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
export default function LegalNotice() {
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
Mentions Légales
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY} italic>
|
||||||
|
Version en vigueur à compter du 10 avril 2024
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’éditeur du Site est SMARTCHAIN, dont le siège social est situé au 23 RUE TAITBOUT, 75009 PARIS 9 avec un capital
|
||||||
|
social de 10 000,00 Euros.
|
||||||
|
<br />
|
||||||
|
N° RCS : 851 103 267 R.C.S. Paris
|
||||||
|
<br />
|
||||||
|
Tel : 06 63 84 83 45
|
||||||
|
<br />
|
||||||
|
N° individuel / d’identification de TVA : FR56851103267
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Directeur de la publication : Konstantin Grouzdev
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Hébergement du Site : SMARTCHAIN
|
||||||
|
<br />
|
||||||
|
Adresse : 23 RUE TAITBOUT, 75009 PARIS 9
|
||||||
|
<br />
|
||||||
|
Représentée par Konstantin Grouzdev - Téléphone 06 63 84 83 45 - Site Internet
|
||||||
|
<br />
|
||||||
|
<a href="https://www.lecoffre.io/" target="_blank" rel="noreferrer">
|
||||||
|
<Typography type="span" typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
https://www.lecoffre.io/
|
||||||
|
</Typography>
|
||||||
|
</a>
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
.root {
|
||||||
|
width: 1056px;
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xl, 32px);
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md, 16px);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,376 @@
|
|||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import Dot from "@Front/Components/Layouts/Legal/LegalInformations/elements/Dot";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
|
import { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
|
||||||
|
import LinkInline from "../elements/LinkInline";
|
||||||
|
|
||||||
|
export default function PrivacyPolicy() {
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
|
Politique de confidentialité
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY} italic>
|
||||||
|
Version en vigueur à compter du 24 avril 2024
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les informations suivantes vous sont communiquées afin que vous puissiez prendre connaissance des engagements en matière
|
||||||
|
de protection des données à caractère personnel de LEcoffre.io, qui agit en tant que responsable du traitement pour les
|
||||||
|
traitements de données à caractère personnel évoqués dans le présent document.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
1. Les données à caractère personnel que nous traitons
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Dans le cadre des traitements de données à caractère personnel, LEcoffre.io collecte et traite les données suivantes en
|
||||||
|
qualité de responsable du traitement :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>Données d'identité (nom, prénom), d'identification (courriel et numéro de téléphone);</Dot>
|
||||||
|
<Dot>Données de paiement;</Dot>
|
||||||
|
<Dot>Données de connexion et de navigation.</Dot>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Il peut également arriver que des Internautes communiquent d’autres données en contactant LEcoffre.io à l’aide de son
|
||||||
|
formulaire de contact.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
2. Les finalités de nos traitements
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les traitements que nous mettons en œuvre le sont pour assurer les finalités suivantes :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>répondre à vos demandes de contacts;</Dot>
|
||||||
|
<Dot>la gestion des demandes de droit d'accès, de suppression, de rectification et d'opposition;</Dot>
|
||||||
|
<Dot>vous permettre d’accéder au Site et à la plateforme LECoffre.io;</Dot>
|
||||||
|
<Dot>vous envoyer par courriel nos offres relatives aux Services, des actualités et bonnes pratiques.</Dot>
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
3. Les fondements juridiques de nos traitements
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Nous ne mettons en œuvre des traitements de données que si au moins l’une des conditions suivantes est remplie :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>votre consentement aux opérations de traitement a été recueilli;</Dot>
|
||||||
|
<Dot>
|
||||||
|
l’existence de notre intérêt légitime, ou de celui d’un tiers, qui justifie que nous mettions en œuvre le traitement
|
||||||
|
de données à caractère personnel concerné;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
l’exécution d’un contrat qui nous lie à vous nécessite que nous mettions en œuvre le traitement de données à
|
||||||
|
caractère personnel concerné.
|
||||||
|
</Dot>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les intérêts légitimes qui sont poursuivis par LEcoffre.io peuvent notamment consister dans le management de son
|
||||||
|
activité, le suivi de ses contrats et la réalisation de ses prestations.
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
4. Les destinataires de vos données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les données à caractère personnel que nous collectons, de même que celles qui sont recueillies ultérieurement, nous sont
|
||||||
|
destinées en notre qualité de responsable du traitement.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Nous veillons à ce que seules les personnes habilitées puissent avoir accès à ces données. Nos prestataires de services
|
||||||
|
peuvent être destinataires de ces données pour réaliser les prestations que nous leur confions. Certaines données
|
||||||
|
personnelles peuvent être adressées à des tiers ou à des autorités légalement habilitées et ce pour satisfaire nos
|
||||||
|
obligations légales, réglementaires ou conventionnelles.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
5. La durée de conservation de vos données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les durées de conservation que nous appliquons à vos données à caractère personnel sont proportionnées aux finalités
|
||||||
|
pour lesquelles elles ont été collectées. En conséquence, nous organisons notre politique de conservation des données de
|
||||||
|
la manière suivante :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<u>Durée de conservation des données techniques</u>
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les données techniques sont conservées pour la durée strictement nécessaire à la réalisation des finalités visées dans
|
||||||
|
la présente politique de confidentialité.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<u>Conservation des données pendant la durée de la relation contractuelle</u>
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Les données à caractère personnel faisant l'objet d'un traitement ne sont pas conservées au-delà du temps nécessaire à
|
||||||
|
l'exécution des obligations définies lors de la conclusion du contrat ou de la durée prédéfinie de la relation
|
||||||
|
contractuelle.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
6. La sécurité de vos données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io a mis en œuvre des mesures pour protéger la confidentialité, la sécurité et l’intégrité de vos données à
|
||||||
|
caractère personnel. L’accès aux données à caractère personnel est restreint aux employés et fournisseurs de service qui
|
||||||
|
ont besoin de connaître ces informations et qui ont été formés pour se conformer aux règles en matière de
|
||||||
|
confidentialité.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io s’engage à ce que vos données à caractère personnel ne soient pas altérées ou endommagées et que des tiers
|
||||||
|
non autorisés n’y aient pas accès.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Nous disposons des mesures de protection physiques, techniques, électroniques, procédurales et organisationnelles
|
||||||
|
appropriées pour protéger les informations personnelles recueillies contre tout traitement non autorisé.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Toutefois, LEcoffre.io ne saurait vous garantir contre toute perte, destruction ou dommages de vos données à caractère
|
||||||
|
personnel.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
7. Les droits qui vous sont reconnus
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Modalités d’exercice de vos droits
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous pouvez exercer vos droits par voie électronique à l’adresse suivante (contact@lecoffre.io) ou par courrier postal à
|
||||||
|
l’adresse LEcoffre.io - 2, Mail Anne-Catherine – 35000 Rennes, en justifiant de votre identité.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Pour ce faire, vous devez indiquer clairement vos nom(s) et prénom(s), l’adresse à laquelle vous souhaitez que la
|
||||||
|
réponse vous soit envoyée et y joindre la photocopie d’un titre d’identité portant votre signature.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Par principe, vous pouvez exercer sans frais l’ensemble de vos droits. Cependant en matière de droit d’accès, il pourra
|
||||||
|
vous être demandé le paiement de frais raisonnables basés sur les coûts administratifs pour toute copie des données que
|
||||||
|
vous demanderez.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Concernant le droit d’information, LEcoffre.io n’aura pas l’obligation d’y donner suite lorsque vous disposez déjà des
|
||||||
|
informations dont vous sollicitez la communication.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io vous informera si elle ne peut donner suite à vos demandes.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
LEcoffre.io tient à vous informer que le non-renseignement ou la modification de vos données sont susceptibles d’avoir
|
||||||
|
des conséquences dans le traitement de certaines demandes dans le cadre de l’exécution des relations contractuelles et
|
||||||
|
que votre demande au titre de l’exercice de vos droits sera conservée à des fins de suivi.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
L’ensemble des droits dont vous bénéficiez est détaillé ci-dessous :
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit à l’information
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous pouvez exercer vos droits par voie électronique à l’adresse suivante (contact@lecoffre.io) ou par courrier postal à
|
||||||
|
l’adresse LEcoffre.io - 2, Mail Anne-Catherine – 35000 Rennes, en justifiant de votre identité.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Si nous décidons de traiter des données pour des finalités autres que celles indiquées, toutes les informations
|
||||||
|
relatives à ces nouvelles finalités vous seront communiquées.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit d’accès et à la rectification de vos données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous disposez du droit d’accéder et de faire rectifier vos données personnelles, que vous pouvez exercer auprès de
|
||||||
|
LEcoffre.io par voie électronique à l’adresse
|
||||||
|
<LinkInline text="contact@lecoffre.io" href="mailto:contact@lecoffre.io" />
|
||||||
|
ou par courrier postal à l’adresse LEcoffre.io – 2, Mail Anne-Catherine – 35000 Rennes.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
A ce titre, vous avez la confirmation que vos données personnelles sont ou ne sont pas traitées et lorsqu’elles le sont,
|
||||||
|
et disposez de l’accès à vos données ainsi qu’aux informations concernant :
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>les finalités du traitement ;</Dot>
|
||||||
|
<Dot>les catégories de données personnelles concernées ;</Dot>
|
||||||
|
<Dot>
|
||||||
|
les destinataires ou catégories de destinataires ainsi que les organisations internationales auxquels les données
|
||||||
|
personnelles ont été ou seront communiquées, en particulier les destinataires qui sont établis dans des pays tiers ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
lorsque cela est possible, la durée de conservation des données personnelles envisagée ou, lorsque ce n’est pas
|
||||||
|
possible, les critères utilisés pour déterminer cette durée ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
l’existence du droit de demander au responsable du traitement la rectification ou l’effacement de vos données
|
||||||
|
personnelles, du droit de demander une limitation du traitement de vos données personnelles, du droit de vous
|
||||||
|
opposer à ce traitement ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>le droit d’introduire une réclamation auprès d’une autorité de contrôle ;</Dot>
|
||||||
|
<Dot>
|
||||||
|
des informations relatives à la source des données quand elles ne sont pas collectées directement auprès des
|
||||||
|
personnes concernées ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
l’existence d’une prise de décision automatisée, y compris de profilage, et dans ce dernier cas, des informations
|
||||||
|
utiles concernant la logique sous-jacente, ainsi que l’importance et les conséquences prévues de ce traitement pour
|
||||||
|
les personnes concernées.
|
||||||
|
</Dot>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous pouvez nous demander que vos données personnelles soient, selon les cas, rectifiées, complétées si elles sont
|
||||||
|
inexactes, incomplètes, équivoques, périmées.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit à l’effacement de vos données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous pouvez nous demander l’effacement de vos données personnelles lorsque l’un des motifs suivants s’applique :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>
|
||||||
|
les données personnelles ne sont plus nécessaires au regard des finalités pour lesquelles elles ont été collectées
|
||||||
|
ou traitées d’une autre manière ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>vous retirez le consentement préalablement donné ;</Dot>
|
||||||
|
<Dot>
|
||||||
|
vous vous opposez au traitement de vos données personnelles lorsqu’il n’existe pas de motif légal audit traitement ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
le traitement de données personnelles n’est pas conforme aux dispositions de la législation et de la réglementation
|
||||||
|
applicable ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>
|
||||||
|
vos données personnelles ont été collectées dans le cadre de l’offre de services de la société de l’information aux
|
||||||
|
enfants âgés de moins de 16 ans.
|
||||||
|
</Dot>
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Néanmoins, l’exercice de ce droit ne sera pas possible lorsque la conservation de vos données personnelles est
|
||||||
|
nécessaire au regard de la législation ou de la réglementation et notamment par exemple pour la constatation, l’exercice
|
||||||
|
ou la défense de droits en justice.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit à la limitation des traitements de données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous pouvez demander la limitation du traitement de vos données personnelles dans les cas prévus par la législation et
|
||||||
|
la réglementation.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit de vous opposer aux traitements de données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous avez le droit de vous opposer à un traitement de données personnelles vous concernant lorsque le traitement est
|
||||||
|
fondé sur l’intérêt légitime du responsable du traitement.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit à la portabilité de vos données
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous disposez du droit à la portabilité de vos données personnelles, lorsque :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
<Dot>
|
||||||
|
le traitement est fondé sur le consentement en application de l’article 6, paragraphe 1, point a), ou de l’article
|
||||||
|
9, paragraphe 2, point a), ou sur un contrat en application de l’article 6, paragraphe 1, point b) du RGPD ;
|
||||||
|
</Dot>
|
||||||
|
<Dot>le traitement est effectué à l'aide de procédés automatisés.</Dot>
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit de retirer votre consentement
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Lorsque les traitements de données que nous mettons en œuvre sont fondés sur votre consentement, vous pouvez le retirer
|
||||||
|
à n’importe quel moment. Nous cessons alors de traiter vos données à caractère personnel sans que les opérations
|
||||||
|
antérieures pour lesquelles vous aviez consenti ne soient remises en cause.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit d’introduire un recours
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Si vous considérez que LEcoffre.io ne respecte pas ses obligations au regard de vos données à caractère personnel, vous
|
||||||
|
pouvez adresser une plainte ou une demande auprès de l’autorité compétente. En France, l’autorité compétente est la Cnil
|
||||||
|
à laquelle vous pouvez adresser une demande par voie électronique en cliquant sur le lien suivant{" "}
|
||||||
|
<LinkInline text="https://www.cnil.fr/fr/plaintes/internet" href="https://www.cnil.fr/fr/plaintes/internet" />
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Droit de définir des directives post-mortem
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous avez la possibilité de définir des directives relatives à la conservation, à l’effacement et à la communication de
|
||||||
|
vos données personnelles après votre décès et ce auprès d’un tiers de confiance, certifié et chargé de faire respecter
|
||||||
|
la volonté du défunt, conformément aux exigences du cadre juridique applicable.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
8. Cookies
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
En naviguant sur notre site, des cookies sont déposés sur votre terminal, soit directement, soit après avoir recueilli
|
||||||
|
votre consentement lorsque la réglementation en matière de cookies le requiert.
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
|
Vous êtes invité à vous référer aux
|
||||||
|
<LinkInline
|
||||||
|
text="Politiques cookies"
|
||||||
|
href={Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Legal.pages.LegalInformations.props.path.replace(
|
||||||
|
"[legalUid]",
|
||||||
|
ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
concernant l’utilisation par LECoffre.io des cookies.
|
||||||
|
</Typography>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Dot({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
● {children}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function LinkInline({ text, href }: { text: string; href: string }) {
|
||||||
|
return (
|
||||||
|
<a href={href} target="_blank" rel="noreferrer">
|
||||||
|
<Typography type="span" typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||||
|
{" "}
|
||||||
|
{text}{" "}
|
||||||
|
</Typography>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
@ -1,9 +1,13 @@
|
|||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import DefaultLegalDashboard, { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import CGU from "./CGU";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import DefaultLegalDashboard, { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
|
import LegalNotice from "./LegalNotice";
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import PrivacyPolicy from "./PrivacyPolicy";
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
const pdfLinks: Record<ELegalOptions, string> = {
|
const pdfLinks: Record<ELegalOptions, string> = {
|
||||||
"mentions-legales": "https://s3.fr-par.scw.cloud/lecoffre.io-bucket/footer/mentions_legales.pdf",
|
"mentions-legales": "https://s3.fr-par.scw.cloud/lecoffre.io-bucket/footer/mentions_legales.pdf",
|
||||||
@ -16,19 +20,45 @@ const pdfLinks: Record<ELegalOptions, string> = {
|
|||||||
export default function LegalInformations() {
|
export default function LegalInformations() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
let { legalUid } = router.query;
|
let { legalUid } = router.query;
|
||||||
const legalUidTyped = legalUid as ELegalOptions;
|
const legalType = legalUid as ELegalOptions;
|
||||||
|
return <DefaultLegalDashboard>{getLegalInformationContent(legalType)}</DefaultLegalDashboard>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLegalInformationContent(legalType: ELegalOptions) {
|
||||||
|
switch (legalType) {
|
||||||
|
case ELegalOptions.LEGAL_MENTIONS:
|
||||||
|
return <LegalNotice />;
|
||||||
|
case ELegalOptions.CGU:
|
||||||
|
return <CGU />;
|
||||||
|
case ELegalOptions.CGS:
|
||||||
return (
|
return (
|
||||||
<DefaultLegalDashboard>
|
<object data={pdfLinks[legalType]} type="application/pdf" width="100%" height="100%" className={classes["pdf-viewer"]}>
|
||||||
<object data={pdfLinks[legalUidTyped]} type="application/pdf" width="100%" height="100%" className={classes["pdf-viewer"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_LG_LIGHT} className={classes["no-pdf-text"]}>
|
<Typography typo={ETypo.TEXT_LG_LIGHT} className={classes["no-pdf-text"]}>
|
||||||
Votre navigateur ne prend pas en charge l'affichage des pdf
|
Votre navigateur ne prend pas en charge l'affichage des pdf
|
||||||
<Typography typo={ETypo.TEXT_LG_LIGHT} type="span">
|
<Typography typo={ETypo.TEXT_LG_LIGHT} type="span">
|
||||||
<Link href={pdfLinks[legalUidTyped]} target="_blank" style={{ color: `var(${ETypoColor.COLOR_PRIMARY_500})` }}>
|
<Link href={pdfLinks[legalType]} target="_blank" style={{ color: `var(${ETypoColor.COLOR_PRIMARY_500})` }}>
|
||||||
cliquez ici pour télécharger le pdf.
|
cliquez ici pour télécharger le pdf.
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Typography>
|
</Typography>
|
||||||
</object>
|
</object>
|
||||||
</DefaultLegalDashboard>
|
|
||||||
);
|
);
|
||||||
|
case ELegalOptions.POLITIQUE_DE_CONFIDENTIALITE:
|
||||||
|
return <PrivacyPolicy />;
|
||||||
|
case ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES:
|
||||||
|
return (
|
||||||
|
<object data={pdfLinks[legalType]} type="application/pdf" width="100%" height="100%" className={classes["pdf-viewer"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_LIGHT} className={classes["no-pdf-text"]}>
|
||||||
|
Votre navigateur ne prend pas en charge l'affichage des pdf
|
||||||
|
<Typography typo={ETypo.TEXT_LG_LIGHT} type="span">
|
||||||
|
<Link href={pdfLinks[legalType]} target="_blank" style={{ color: `var(${ETypoColor.COLOR_PRIMARY_500})` }}>
|
||||||
|
cliquez ici pour télécharger le pdf.
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
</object>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,22 @@
|
|||||||
@import "@Themes/constants.scss";
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
|
margin: 80px auto;
|
||||||
|
width: 474px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
gap: var(--spacing-xl, 32px);
|
||||||
max-width: 530px;
|
|
||||||
margin: auto;
|
.title-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 32px 0;
|
|
||||||
text-align: center;
|
|
||||||
@media (max-width: $screen-s) {
|
@media (max-width: $screen-s) {
|
||||||
font-family: 48px;
|
width: 100%;
|
||||||
}
|
margin: auto;
|
||||||
}
|
padding: var(--spacing-md, 16px);
|
||||||
|
|
||||||
.loader {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.forget-password {
|
|
||||||
margin-top: 32px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user