Merge branch 'staging' into preprod

This commit is contained in:
Maxime Lalo 2024-07-24 17:53:55 +02:00
commit 6c43cdb9b6
41 changed files with 635 additions and 527 deletions

View File

@ -49,7 +49,7 @@ export default function Alert(props: IProps) {
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.COLOR_NEUTRAL_950}> <Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
{title} {title}
</Typography> </Typography>
<Typography typo={ETypo.TEXT_MD_light} color={ETypoColor.COLOR_NEUTRAL_700}> <Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.COLOR_NEUTRAL_700}>
{description} {description}
</Typography> </Typography>
</div> </div>

View File

@ -1,48 +0,0 @@
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo } from "../Typography";
import ChevronIcon from "@Assets/Icons/chevron.svg";
import Image from "next/image";
import WarningBadge from "../WarningBadge";
export type IBlock = {
name: string;
id: string;
selected: boolean;
rightIcon?: JSX.Element;
hasFlag?: boolean;
};
type IProps = {
blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void;
};
export default function BlockList({ blocks, onSelectedBlock }: IProps) {
const selectBlock = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === e.currentTarget.id)!);
},
[blocks, onSelectedBlock],
);
return (
<div>
{blocks.map((folder) => {
return (
<div onClick={selectBlock} key={folder.id} id={folder.id}>
<div className={classes["root"]} data-selected={folder.selected.toString()}>
<div className={classes["left-side"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR}>{folder.name}</Typography>
</div>
<div className={classes["right-side"]}>
{folder.hasFlag && <WarningBadge />}
{folder.rightIcon}
<Image alt="chevron" src={ChevronIcon} />
</div>
</div>
</div>
);
})}
</div>
);
}

View File

@ -1,16 +1,12 @@
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import Link from "next/link";
import { NextRouter, useRouter } from "next/router"; import { NextRouter, useRouter } from "next/router";
import React from "react"; import React from "react";
import BlockList, { IBlock } from "../BlockList";
import Button from "../Button";
import SearchBar from "../SearchBar";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules"; import { IBlock } from "../SearchBlockList/BlockList/Block";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule"; import SearchBlockList from "../SearchBlockList";
type IProps = { type IProps = {
folders: OfficeFolder[]; folders: OfficeFolder[];
@ -44,33 +40,9 @@ class FolderArchivedListContainerClass extends React.Component<IPropsClass, ISta
} }
public override render(): JSX.Element { public override render(): JSX.Element {
const navigatePath = Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path;
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList blocks={this.state.blocks} onSelectedBlock={this.onSelectedFolder} />
<div className={classes["searchbar"]}>
<SearchBar onChange={this.filterFolders} placeholder="Chercher un dossier" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList blocks={this.state.blocks} onSelectedBlock={this.onSelectedFolder} />
</div>
</div>
{!this.props.isArchived && (
<div>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.create,
name: AppRuleNames.officeFolders,
},
]}>
<Link href={navigatePath}>
<Button fullwidth={true}>Créer un dossier</Button>
</Link>
</Rules>
</div>
)}
</div> </div>
); );
} }
@ -106,9 +78,10 @@ class FolderArchivedListContainerClass extends React.Component<IPropsClass, ISta
return [...pendingFolders, ...otherFolders].map((folder) => { return [...pendingFolders, ...otherFolders].map((folder) => {
return { return {
id: folder.uid!, id: folder.uid!,
name: folder.folder_number! + " - " + folder.name!, primaryText: folder.name,
selected: this.props.selectedFolder === folder.uid, isActive: this.props.selectedFolder === folder.uid,
hasFlag: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED), secondaryText: folder.folder_number,
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
}; };
}); });
} }

View File

@ -1,16 +1,12 @@
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import Link from "next/link"; import { useRouter } from "next/router";
import { NextRouter, useRouter } from "next/router"; import React, { useCallback, useEffect } from "react";
import React from "react";
import BlockList, { IBlock } from "../BlockList";
import Button from "../Button";
import SearchBar from "../SearchBar";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules"; import { IBlock } from "../SearchBlockList/BlockList/Block";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule"; import SearchBlockList from "../SearchBlockList";
type IProps = { type IProps = {
folders: OfficeFolder[]; folders: OfficeFolder[];
@ -19,68 +15,17 @@ type IProps = {
onCloseLeftSide?: () => void; onCloseLeftSide?: () => void;
}; };
type IPropsClass = IProps & { export default function FolderListContainer(props: IProps) {
router: NextRouter; const router = useRouter();
selectedFolder: string; const { folderUid } = router.query;
}; const { folders, isArchived } = props;
type IState = { const redirectPath: string = isArchived
filteredFolders: OfficeFolder[];
blocks: IBlock[];
};
class FolderListContainerClass extends React.Component<IPropsClass, IState> {
private redirectPath: string = this.props.isArchived
? Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.pages.FolderInformation.props.path ? Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.pages.FolderInformation.props.path
: Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path; : Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path;
public constructor(props: IPropsClass) {
super(props);
this.state = {
filteredFolders: this.props.folders,
blocks: this.getBlocks(this.props.folders),
};
this.filterFolders = this.filterFolders.bind(this);
this.onSelectedFolder = this.onSelectedFolder.bind(this);
}
public override render(): JSX.Element { const getBlocks = useCallback(
const navigatePath = Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path; (folders: OfficeFolder[]): IBlock[] => {
return (
<div className={classes["root"]}>
<div className={classes["header"]}>
<div className={classes["searchbar"]}>
<SearchBar onChange={this.filterFolders} placeholder="Chercher un dossier" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList blocks={this.state.blocks} onSelectedBlock={this.onSelectedFolder} />
</div>
</div>
{!this.props.isArchived && (
<div>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.create,
name: AppRuleNames.officeFolders,
},
]}>
<Link href={navigatePath}>
<Button fullwidth={true}>Créer un dossier</Button>
</Link>
</Rules>
</div>
)}
</div>
);
}
public override componentDidUpdate(prevProps: Readonly<IPropsClass>, prevState: Readonly<IState>, snapshot?: any): void {
if (prevProps.selectedFolder !== this.props.selectedFolder) {
this.setState({ filteredFolders: this.props.folders, blocks: this.getBlocks(this.props.folders) });
}
}
private getBlocks(folders: OfficeFolder[]): IBlock[] {
const pendingFolders = folders const pendingFolders = folders
.filter((folder) => { .filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter( const pendingDocuments = (folder.documents ?? []).filter(
@ -106,45 +51,41 @@ class FolderListContainerClass extends React.Component<IPropsClass, IState> {
return [...pendingFolders, ...otherFolders].map((folder) => { return [...pendingFolders, ...otherFolders].map((folder) => {
return { return {
id: folder.uid!, id: folder.uid!,
name: folder.folder_number! + " - " + folder.name!, primaryText: folder.name,
selected: this.props.selectedFolder === folder.uid, secondaryText: folder.folder_number,
hasFlag: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED), isActive: folderUid === folder.uid,
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
}; };
}); });
} },
[folderUid],
);
private onSelectedFolder(block: IBlock) { const [blocks, setBlocks] = React.useState<IBlock[]>(getBlocks(folders));
const folder = this.props.folders.find((folder) => folder.uid === block.id);
const onSelectedFolder = (block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const folder = folders.find((folder) => folder.uid === block.id);
if (!folder) return; if (!folder) return;
this.props.onSelectedFolder && this.props.onSelectedFolder(folder); props.onSelectedFolder && props.onSelectedFolder(folder);
const path = this.redirectPath.replace("[folderUid]", folder.uid ?? ""); const path = redirectPath.replace("[folderUid]", folder.uid ?? "");
this.props.router.push(path); router.push(path);
} };
private filterFolders(value: string): void { useEffect(() => {
const filteredFolders: OfficeFolder[] = this.props.folders.filter((folder) => { setBlocks(getBlocks(folders));
const name = folder.name.toLowerCase(); }, [folders, getBlocks]);
const number = folder.folder_number.toLowerCase();
value = value.toLowerCase();
if (folder.customers) {
const customerNames = folder.customers
.map((customer) => {
return `${customer.contact?.first_name.toLowerCase()} ${customer.contact?.last_name.toLowerCase()}`;
})
.join(", ");
return name.includes(value) || number.includes(value) || customerNames.includes(value); return (
} <div className={classes["root"]}>
<SearchBlockList
return name.includes(value) || number.includes(value); blocks={blocks}
}); onSelectedBlock={onSelectedFolder}
bottomButton={{
this.setState({ filteredFolders, blocks: this.getBlocks(filteredFolders) }); link: Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path,
} text: "Créer un dossier",
} }}
/>
export default function FolderListContainer(props: IProps) { </div>
const router = useRouter(); );
const { folderUid } = router.query;
return <FolderListContainerClass {...props} router={router} selectedFolder={folderUid as string} />;
} }

View File

@ -0,0 +1,48 @@
@import "@Themes/constants.scss";
.root {
border-top: 1px solid var(--footer-border, #d7dce0);
background: var(--footer-background, #fff);
padding: var(--spacing-1-5) 0;
font-family: var(--font-title-family, Poppins);
font-size: 12px;
font-weight: var(--font-text-weight-regular, 400);
letter-spacing: 0.06px;
.sub-root {
display: flex;
align-items: center;
gap: var(--Radius-lg, 16px);
white-space: nowrap;
padding: 0 360px;
//make it sticky
@media (max-width: 1023px) {
padding: 0 12px;
}
}
@media (max-width: 660px) or (min-width: 768px) {
.tablet {
display: none;
}
}
@media (min-width: 660px) {
.mobile {
display: none;
}
}
@media (max-width: 769px) {
.desktop {
display: none;
}
}
.separator {
&::before {
content: "|";
}
}
}

View File

@ -0,0 +1,20 @@
import React from "react";
import classes from "./classes.module.scss";
type IProps = {
className?: string;
};
export default function Desktop({ className }: IProps) {
return (
<div className={[classes["sub-root"], className].join(" ")}>
<span>© Copyright lecoffre 2024</span>
<span className={classes["separator"]} />
<a href="/terms">Conditions d'utilisation</a>
<span className={classes["separator"]} />
<a href="/privacy">Politique de confidentialité</a>
<span className={classes["separator"]} />
<a href="/cookies">Politique des cookies</a>
</div>
);
}

View File

@ -0,0 +1,19 @@
import React from "react";
import classes from "./classes.module.scss";
import Mobile from "./mobile";
import Desktop from "./desktop";
import Tablet from "./tablet";
type IProps = {
className?: string;
};
export default function Footer({ className }: IProps) {
return (
<footer className={[classes["root"], className].join(" ")}>
<Mobile className={classes["mobile"]} />
<Tablet className={classes["tablet"]} />
<Desktop className={classes["desktop"]} />
</footer>
);
}

View File

@ -0,0 +1,18 @@
import React from "react";
import classes from "./classes.module.scss";
type IProps = {
className?: string;
};
export default function Mobile({ className }: IProps) {
return (
<div className={[classes["sub-root"], className].join(" ")}>
<span>© Lecoffre 2024</span>
<span className={classes["separator"]} />
<a href="/terms">Juridiques</a>
<span className={classes["separator"]} />
<a href="/cookies">Cookies</a>
</div>
);
}

View File

@ -0,0 +1,20 @@
import React from "react";
import classes from "./classes.module.scss";
type IProps = {
className?: string;
};
export default function Tablet({ className }: IProps) {
return (
<div className={[classes["sub-root"], className].join(" ")}>
<span>© Lecoffre 2024</span>
<span className={classes["separator"]} />
<a href="/terms">Conditions d'utilisation</a>
<span className={classes["separator"]} />
<a href="/privacy">Politique de confidentialité</a>
<span className={classes["separator"]} />
<a href="/cookies">Politique des cookies</a>
</div>
);
}

View File

@ -2,30 +2,67 @@
.root { .root {
display: flex; display: flex;
align-items: center; padding: var(--spacing-2, 16px) var(--spacing-sm, 8px);
padding-left: 24px; align-items: flex-start;
background-color: var(--color-generic-white); gap: var(--spacing-2, 16px);
border: 1px solid var(--color-neutral-200);
position: relative;
.fake-placeholder { border-radius: var(--input-radius, 0px);
position: absolute; border: 1px solid var(--input-main-border-default, #d7dce0);
left: 47px; background: var(--input-background, #fff);
top: 24px;
color: var(--color-neutral-500); &:hover {
pointer-events: none; border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-hovered, #b4bec5);
background: var(--input-background, #fff);
} }
&[data-has-value="true"] {
border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-filled, #6d7e8a);
background: var(--input-background, #fff);
}
&[data-is-focused="true"] {
border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-focused, #005bcb);
background: var(--input-background, #fff);
}
.input-container {
display: flex;
flex: 1;
gap: 8px;
padding: 0px var(--spacing-2, 16px);
.input { .input {
border: 0; flex: 1;
margin-left: 8px; border: none;
padding: 24px 0;
width: 100%; color: var(--input-placeholder-filled, #24282e);
font-family: "Inter", sans-serif;
/* text/md/semibold */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: var(--font-text-weight-semibold, 600);
font-size: 18px; line-height: normal;
line-height: 22px; letter-spacing: 0.08px;
color: var(--color-neutral-500); width: 100%;
&::placeholder {
color: var(--input-placeholder-empty, #6d7e8a);
/* text/md/regular */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-regular, 400);
line-height: normal;
letter-spacing: 0.08px;
}
}
.cross {
cursor: pointer;
}
} }
} }

View File

@ -1,54 +1,45 @@
import LoopIcon from "@Assets/Icons/loop.svg"; import React, { useCallback } from "react";
import Image from "next/image";
import React from "react";
import Typography, { ETypo, ETypoColor } from "../Typography";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
type IProps = { type IProps = {
onChange?: (input: string) => void; onChange?: (input: string) => void;
placeholder?: string; placeholder?: string;
}; };
type IState = { export default function SearchBar({ onChange, placeholder = "Rechercher" }: IProps) {
hasValue: boolean; const [isFocused, setIsFocused] = React.useState(false);
}; const [value, setValue] = React.useState("");
export default class SearchBar extends React.Component<IProps, IState> { const changeValue = useCallback(
static defaultProps = { (value: string) => {
placeholder: "Search", setValue(value);
}; onChange && onChange(value);
},
public constructor(props: IProps) { [onChange],
super(props);
this.state = {
hasValue: false,
};
this.doesInputHaveValue = this.doesInputHaveValue.bind(this);
this.onChange = this.onChange.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Image src={LoopIcon} alt="Loop icon" />
{!this.state.hasValue && (
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_ERROR_600}>
<div className={classes["fake-placeholder"]}>{this.props.placeholder}</div>
</Typography>
)}
<input type="text" placeholder="" className={classes["input"]} onChange={this.onChange} />
</div>
); );
}
private onChange(event: React.ChangeEvent<HTMLInputElement>) { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value);
const hasValue = event.target.value.length > 0; const handleFocus = () => setIsFocused(true);
this.doesInputHaveValue(hasValue); const handleBlur = () => setIsFocused(false);
if (!this.props.onChange) return; const clearValue = () => changeValue("");
this.props.onChange(event.target.value);
}
private doesInputHaveValue(hasValue: boolean) { return (
this.setState({ hasValue }); <label className={classes["root"]} data-is-focused={isFocused} data-has-value={value !== ""}>
} <div className={classes["input-container"]}>
<MagnifyingGlassIcon width="24" height="24" />
<input
type="text"
value={value}
placeholder={placeholder}
className={classes["input"]}
onChange={handleOnChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
{value !== "" && <XMarkIcon width="24" height="24" className={classes["cross"]} onClick={clearValue} />}
</div>
</label>
);
} }

View File

@ -0,0 +1,29 @@
.root {
background: var(--navigation-button-background-default, #f7f8f8);
cursor: pointer;
&:hover {
background: var(--navigation-button-background-actived, #edeff1);
}
&[data-is-active="true"] {
border-left: 3px solid var(--navigation-border, #005bcb);
background: var(--navigation-button-background-actived, #edeff1);
}
.content {
display: flex;
padding: var(--spacing-md, 16px) var(--spacing-lg, 24px);
align-items: center;
gap: var(--spacing-sm, 8px);
.text-container {
flex: 1;
}
}
.separator {
width: 100%;
height: 1px;
background: var(--separator-stroke-light, #d7dce0);
}
}

View File

@ -0,0 +1,46 @@
import { ChevronRightIcon } from "@heroicons/react/24/outline";
import classes from "./classes.module.scss";
import { useCallback } from "react";
import { ExclamationCircleIcon } from "@heroicons/react/20/solid";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
export type IBlock = {
id: string;
primaryText: string;
secondaryText?: string;
showAlert?: boolean;
isActive?: boolean;
};
type IProps = IBlock & {
hasSeparator?: boolean;
onClick?: (id: string) => void;
};
export default function Block(props: IProps) {
const { primaryText, secondaryText, showAlert = false, hasSeparator = true, isActive = false, onClick } = props;
const handleOnClick = useCallback(() => onClick && onClick(props.id), [onClick, props.id]);
return (
<div className={classes["root"]} data-is-active={isActive} onClick={handleOnClick}>
<div className={classes["content"]}>
<div className={classes["text-container"]}>
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{secondaryText}
</Typography>
<Typography
typo={ETypo.TEXT_MD_BOLD}
color={isActive ? ETypoColor.NAVIGATION_BUTTON_CONTRAST_ACTIVE : ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{primaryText}
</Typography>
</div>
{showAlert && (
<ExclamationCircleIcon width="24" height="24" className={classes["icon"]} color={"var(--warning-default-base)"} />
)}
<ChevronRightIcon width="24" height="24" className={classes["icon"]} color={"var(--button-icon-button-default-default)"} />
</div>
{hasSeparator && <div className={classes["separator"]} />}
</div>
);
}

View File

@ -0,0 +1,23 @@
import React, { useCallback } from "react";
import Block, { IBlock } from "./Block";
type IProps = {
blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void;
};
export default function BlockList({ blocks, onSelectedBlock }: IProps) {
const selectBlock = useCallback(
(id: string) => {
onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === id)!);
},
[blocks, onSelectedBlock],
);
return (
<div>
{blocks.map((block) => {
return <Block {...block} hasSeparator onClick={selectBlock} key={block.id} />;
})}
</div>
);
}

View File

@ -0,0 +1,28 @@
.root {
width: 336px;
height: 100%;
max-height: 100%;
border-radius: var(--navigation-radius, 0px);
background: var(--navigation-button-background-default, #f7f8f8);
display: flex;
flex-direction: column;
justify-content: flex-start;
.block-list {
overflow-y: auto;
::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.searchbar {
padding: var(--spacing-md, 16px);
}
.bottom-container {
margin-top: auto;
}
}

View File

@ -0,0 +1,78 @@
import { useCallback, useEffect, useRef, useState } from "react";
import BlockList from "./BlockList";
import { IBlock } from "./BlockList/Block";
import classes from "./classes.module.scss";
import SearchBar from "../SearchBar";
import Button from "../Button";
import { useRouter } from "next/router";
type IProps = {
blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void;
bottomButton?: {
text: string;
link: string;
};
};
export default function SearchBlockList(props: IProps) {
const { blocks, onSelectedBlock, bottomButton } = props;
const router = useRouter();
const [blocksShowing, setBlocksShowing] = useState<IBlock[]>(blocks);
const bottomButtonRef = useRef<HTMLAnchorElement>(null);
const searchBarRef = useRef<HTMLDivElement>(null);
const handleSearchChange = useCallback(
(value: string) => {
const filteredBlocks = blocks.filter((block) => {
const name = block.primaryText.toLowerCase();
const number = block.secondaryText?.toLowerCase();
value = value.toLowerCase();
return name.includes(value) || number?.includes(value);
});
setBlocksShowing(filteredBlocks);
},
[blocks],
);
const getHeight = useCallback(() => {
let heightToSubstract = 0;
if (bottomButton && bottomButtonRef.current) {
heightToSubstract += bottomButtonRef.current.clientHeight;
}
if (searchBarRef.current) {
heightToSubstract += searchBarRef.current.clientHeight;
}
return {
maxHeight: `calc(100% - ${heightToSubstract}px)`,
};
}, [bottomButton]);
const redirectOnBottomButtonClick = useCallback(() => {
if (!bottomButton) return;
router.push(bottomButton.link);
}, [bottomButton, router]);
useEffect(() => {
setBlocksShowing(blocks);
}, [blocks]);
return (
<div className={classes["root"]}>
<div className={classes["searchbar"]} ref={searchBarRef}>
<SearchBar placeholder="Chercher" onChange={handleSearchChange} />
</div>
<div className={classes["block-list"]} style={getHeight()}>
<BlockList blocks={blocksShowing} onSelectedBlock={onSelectedBlock} />
</div>
{bottomButton && (
<div className={classes["bottom-container"]}>
<Button fullwidth onClick={redirectOnBottomButtonClick}>
{bottomButton.text}
</Button>
</div>
)}
</div>
);
}

View File

@ -28,25 +28,25 @@ export enum ETypo {
TEXT_LG_SEMIBOLD = "text-lg-semibold", TEXT_LG_SEMIBOLD = "text-lg-semibold",
TEXT_LG_REGULAR = "text-lg-regular", TEXT_LG_REGULAR = "text-lg-regular",
TEXT_LG_UPPERCASE = "text-lg-uppercase", TEXT_LG_UPPERCASE = "text-lg-uppercase",
TEXT_LG_light = "text-lg-light", TEXT_LG_LIGHT = "text-lg-light",
TEXT_MD_BOLD = "text-md-bold", TEXT_MD_BOLD = "text-md-bold",
TEXT_MD_SEMIBOLD = "text-md-semibold", TEXT_MD_SEMIBOLD = "text-md-semibold",
TEXT_MD_REGULAR = "text-md-regular", TEXT_MD_REGULAR = "text-md-regular",
TEXT_MD_UPPERCASE = "text-md-uppercase", TEXT_MD_UPPERCASE = "text-md-uppercase",
TEXT_MD_light = "text-md-light", TEXT_MD_LIGHT = "text-md-light",
TEXT_SM_BOLD = "text-sm-bold", TEXT_SM_BOLD = "text-sm-bold",
TEXT_SM_SEMIBOLD = "text-sm-semibold", TEXT_SM_SEMIBOLD = "text-sm-semibold",
TEXT_SM_REGULAR = "text-sm-regular", TEXT_SM_REGULAR = "text-sm-regular",
TEXT_SM_UPPERCASE = "text-sm-uppercase", TEXT_SM_UPPERCASE = "text-sm-uppercase",
TEXT_SM_light = "text-sm-light", TEXT_SM_LIGHT = "text-sm-light",
TEXT_XS_BOLD = "text-xs-bold", TEXT_XS_BOLD = "text-xs-bold",
TEXT_XS_SEMIBOLD = "text-xs-semibold", TEXT_XS_SEMIBOLD = "text-xs-semibold",
TEXT_XS_REGULAR = "text-xs-regular", TEXT_XS_REGULAR = "text-xs-regular",
TEXT_XS_UPPERCASE = "text-xs-uppercase", TEXT_XS_UPPERCASE = "text-xs-uppercase",
TEXT_XS_light = "text-xs-light", TEXT_XS_LIGHT = "text-xs-light",
CAPTION_BOLD = "caption-bold", CAPTION_BOLD = "caption-bold",
CAPTION_SEMIBOLD = "caption-semibold", CAPTION_SEMIBOLD = "caption-semibold",
@ -153,6 +153,8 @@ export enum ETypoColor {
CONTRAST_DEFAULT = "--contrast-default", CONTRAST_DEFAULT = "--contrast-default",
CONTRAST_HOVERED = "--contrast-hovered", CONTRAST_HOVERED = "--contrast-hovered",
ERROR_WEAK_CONTRAST = "--error-weak-contrast", ERROR_WEAK_CONTRAST = "--error-weak-contrast",
NAVIGATION_BUTTON_CONTRAST_DEFAULT = "--navigation-button-contrast-default",
NAVIGATION_BUTTON_CONTRAST_ACTIVE = "--navigation-button-contrast-active",
} }
export default function Typography(props: IProps) { export default function Typography(props: IProps) {

View File

@ -6,6 +6,9 @@
border-bottom: 1px solid var(--tabs-stroke, #d7dce0); border-bottom: 1px solid var(--tabs-stroke, #d7dce0);
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
gap: var(--spacing-1-5, 12px);
&[data-is-selected="true"] { &[data-is-selected="true"] {
border-bottom: 2px solid var(--tabs-contrast-actived, #24282e); border-bottom: 2px solid var(--tabs-contrast-actived, #24282e);
} }

View File

@ -3,6 +3,7 @@ import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import useHoverable from "@Front/Hooks/useHoverable"; import useHoverable from "@Front/Hooks/useHoverable";
import { ITabValue } from ".."; import { ITabValue } from "..";
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";
export type ITab = { export type ITab = {
label: React.ReactNode; label: React.ReactNode;
}; };
@ -11,6 +12,7 @@ export type IProps<T> = {
onSelect: (value: ITabValue<T>) => void; onSelect: (value: ITabValue<T>) => void;
value: ITabValue<T>; value: ITabValue<T>;
isSelected: boolean; isSelected: boolean;
hasWarning?: boolean;
} & ITab; } & ITab;
export default function HorizontalTabs<T>(props: IProps<T>) { export default function HorizontalTabs<T>(props: IProps<T>) {
@ -29,6 +31,7 @@ export default function HorizontalTabs<T>(props: IProps<T>) {
color={!isHovered && !props.isSelected ? ETypoColor.TABS_CONTRAST_DEFAULT : ETypoColor.TABS_CONTRAST_ACTIVATED}> color={!isHovered && !props.isSelected ? ETypoColor.TABS_CONTRAST_DEFAULT : ETypoColor.TABS_CONTRAST_ACTIVATED}>
{props.label} {props.label}
</Typography> </Typography>
{props.hasWarning && <ExclamationCircleIcon width="24" height="24" color="var(--tabs-contrast-warning)" />}
</div> </div>
); );
} }

View File

@ -8,11 +8,12 @@ import useOpenable from "@Front/Hooks/useOpenable";
export type ITabValue<T> = T & { export type ITabValue<T> = T & {
id: unknown; id: unknown;
} };
type ITabInternal<T> = ITab & { type ITabInternal<T> = ITab & {
key?: string; key?: string;
value: ITabValue<T>; value: ITabValue<T>;
hasWarning?: boolean;
}; };
type IProps<T> = { type IProps<T> = {
@ -100,6 +101,7 @@ export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
value={element.value} value={element.value}
onSelect={handleSelect} onSelect={handleSelect}
isSelected={element.value.id === selectedTab.id} isSelected={element.value.id === selectedTab.id}
hasWarning={element.hasWarning}
/> />
))} ))}
</div> </div>
@ -112,6 +114,7 @@ export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
value={element.value} value={element.value}
onSelect={handleSelect} onSelect={handleSelect}
isSelected={element.value.id === selectedTab.id} isSelected={element.value.id === selectedTab.id}
hasWarning={element.hasWarning}
/> />
))} ))}
</div> </div>

View File

@ -1,11 +1,11 @@
import React, { useCallback, useState } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import SearchBar from "@Front/Components/DesignSystem/SearchBar";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = { type IProps = {
collaborators: User[]; collaborators: User[];
@ -14,22 +14,9 @@ type IProps = {
}; };
export default function CollaboratorListContainer(props: IProps) { export default function CollaboratorListContainer(props: IProps) {
const [filteredUsers, setFilteredUsers] = useState<User[]>(props.collaborators);
const router = useRouter(); const router = useRouter();
const { collaboratorUid } = router.query; const { collaboratorUid } = router.query;
const filterUsers = useCallback(
(input: string) => {
const filteredUsers = props.collaborators.filter((user) => {
return (
user.contact?.first_name?.toLowerCase().includes(input.toLowerCase()) ||
user.contact?.last_name?.toLowerCase().includes(input.toLowerCase())
);
});
setFilteredUsers(filteredUsers);
},
[props.collaborators],
);
const onSelectedBlock = useCallback( const onSelectedBlock = useCallback(
(block: IBlock) => { (block: IBlock) => {
@ -42,35 +29,16 @@ export default function CollaboratorListContainer(props: IProps) {
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList
<div className={classes["searchbar"]}> blocks={props.collaborators.map((user) => {
<SearchBar onChange={filterUsers} placeholder="Chercher un collaborateur" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList
blocks={filteredUsers.map((user) => {
return { return {
name: user.contact?.first_name + " " + user.contact?.last_name, primaryText: user.contact?.first_name + " " + user.contact?.last_name,
id: user.uid!, id: user.uid!,
selected: user.uid === collaboratorUid, isActive: user.uid === collaboratorUid,
rightIcon: user.seats?.some((seat) => new Date(seat.subscription!.end_date) >= new Date()) ? (
<div
style={{
height: "12px",
width: "12px",
borderRadius: "100px",
backgroundColor: "var(--color-success-600)",
}}
/>
) : (
<></>
),
}; };
})} })}
onSelectedBlock={onSelectedBlock} onSelectedBlock={onSelectedBlock}
/> />
</div> </div>
</div>
</div>
); );
} }

View File

@ -1,14 +1,12 @@
import DeedTypes from "@Front/Api/LeCoffreApi/Admin/DeedTypes/DeedTypes"; import DeedTypes from "@Front/Api/LeCoffreApi/Admin/DeedTypes/DeedTypes";
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import Button from "@Front/Components/DesignSystem/Button";
import SearchBar from "@Front/Components/DesignSystem/SearchBar";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { DeedType } from "le-coffre-resources/dist/Admin"; import { DeedType } from "le-coffre-resources/dist/Admin";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback, useState } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = { type IProps = {
deedTypes: DeedType[]; deedTypes: DeedType[];
@ -17,20 +15,9 @@ type IProps = {
}; };
export default function DeedListContainer(props: IProps) { export default function DeedListContainer(props: IProps) {
const [filteredUsers, setFilteredUsers] = useState<DeedType[]>(props.deedTypes);
const router = useRouter(); const router = useRouter();
const { deedTypeUid } = router.query; const { deedTypeUid } = router.query;
const filterDeeds = useCallback(
(input: string) => {
const filteredUsers = props.deedTypes.filter((deedType) => {
return deedType.name?.toLowerCase().includes(input.toLowerCase());
});
setFilteredUsers(filteredUsers);
},
[props.deedTypes],
);
const onSelectedBlock = useCallback( const onSelectedBlock = useCallback(
(block: IBlock) => { (block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide(); props.onCloseLeftSide && props.onCloseLeftSide();
@ -42,28 +29,20 @@ export default function DeedListContainer(props: IProps) {
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList
<div className={classes["searchbar"]}> blocks={props.deedTypes.map((deed) => {
<SearchBar onChange={filterDeeds} placeholder="Chercher un type d'acte" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList
blocks={filteredUsers.map((deed) => {
return { return {
name: deed.name, primaryText: deed.name,
id: deed.uid!, id: deed.uid!,
selected: deedTypeUid === deed.uid, isActive: deedTypeUid === deed.uid,
}; };
})} })}
onSelectedBlock={onSelectedBlock} onSelectedBlock={onSelectedBlock}
bottomButton={{
link: Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
text: "Créer un type d'acte",
}}
/> />
</div> </div>
</div>
<div className={classes["create-container"]}>
<Link href={Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path}>
<Button fullwidth={true}>Créer un type d'acte</Button>
</Link>
</div>
</div>
); );
} }

View File

@ -1,13 +1,11 @@
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import Button from "@Front/Components/DesignSystem/Button";
import SearchBar from "@Front/Components/DesignSystem/SearchBar";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { DocumentType } from "le-coffre-resources/dist/SuperAdmin"; import { DocumentType } from "le-coffre-resources/dist/SuperAdmin";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback, useState } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = { type IProps = {
documentTypes: DocumentType[]; documentTypes: DocumentType[];
@ -16,19 +14,9 @@ type IProps = {
}; };
export default function DocumentTypeListContainer(props: IProps) { export default function DocumentTypeListContainer(props: IProps) {
const [filteredDocumentTypes, setFilteredDocumentTypes] = useState<DocumentType[]>(props.documentTypes);
const router = useRouter(); const router = useRouter();
const { documentTypeUid } = router.query; const { documentTypeUid } = router.query;
const filterDocumentTypes = useCallback(
(input: string) => {
const filteredDocumentTypes = props.documentTypes.filter((documentType) => {
return documentType.name.toLowerCase().includes(input.toLowerCase());
});
setFilteredDocumentTypes(filteredDocumentTypes);
},
[props.documentTypes],
);
const onSelectedBlock = useCallback( const onSelectedBlock = useCallback(
(block: IBlock) => { (block: IBlock) => {
@ -41,28 +29,20 @@ export default function DocumentTypeListContainer(props: IProps) {
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList
<div className={classes["searchbar"]}> blocks={props.documentTypes.map((documentType) => {
<SearchBar onChange={filterDocumentTypes} placeholder="Chercher un document" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList
blocks={filteredDocumentTypes.map((documentType) => {
return { return {
name: documentType.name, primaryText: documentType.name,
id: documentType.uid!, id: documentType.uid!,
selected: documentType.uid === documentTypeUid, isActive: documentType.uid === documentTypeUid,
}; };
})} })}
onSelectedBlock={onSelectedBlock} onSelectedBlock={onSelectedBlock}
bottomButton={{
link: Module.getInstance().get().modules.pages.DocumentTypes.pages.Create.props.path,
text: "Créer un type de document",
}}
/> />
</div> </div>
</div>
<div className={classes["create-container"]}>
<Link href={Module.getInstance().get().modules.pages.DocumentTypes.pages.Create.props.path}>
<Button fullwidth={true}>Créer un type de document</Button>
</Link>
</div>
</div>
); );
} }

View File

@ -1,11 +1,11 @@
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import SearchBar from "@Front/Components/DesignSystem/SearchBar";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { Office } from "le-coffre-resources/dist/SuperAdmin"; import { Office } from "le-coffre-resources/dist/SuperAdmin";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback, useState } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = { type IProps = {
offices: Office[]; offices: Office[];
@ -14,21 +14,9 @@ type IProps = {
}; };
export default function OfficeListContainer(props: IProps) { export default function OfficeListContainer(props: IProps) {
const [filteredOffices, setFilteredOffices] = useState<Office[]>(props.offices);
const router = useRouter(); const router = useRouter();
const { officeUid } = router.query; const { officeUid } = router.query;
const filterOffices = useCallback(
(input: string) => {
const filteredOffices = props.offices.filter((office) => {
return (
office.name.toLowerCase().includes(input.toLowerCase()) || office.crpcen?.toLowerCase().includes(input.toLowerCase())
);
});
setFilteredOffices(filteredOffices);
},
[props.offices],
);
const onSelectedBlock = useCallback( const onSelectedBlock = useCallback(
(block: IBlock) => { (block: IBlock) => {
@ -41,23 +29,16 @@ export default function OfficeListContainer(props: IProps) {
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList
<div className={classes["searchbar"]}> blocks={props.offices.map((office) => {
<SearchBar onChange={filterOffices} placeholder="Chercher un office" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList
blocks={filteredOffices.map((office) => {
return { return {
name: office.crpcen + " - " + office.name, primaryText: office.crpcen + " - " + office.name,
id: office.uid!, id: office.uid!,
selected: office.uid === officeUid, isActive: office.uid === officeUid,
}; };
})} })}
onSelectedBlock={onSelectedBlock} onSelectedBlock={onSelectedBlock}
/> />
</div> </div>
</div>
</div>
); );
} }

View File

@ -1,13 +1,11 @@
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import SearchBar from "@Front/Components/DesignSystem/SearchBar";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { OfficeRole } from "le-coffre-resources/dist/Admin"; import { OfficeRole } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback, useState } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Link from "next/link"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import Button from "@Front/Components/DesignSystem/Button"; import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = { type IProps = {
roles: OfficeRole[]; roles: OfficeRole[];
@ -16,21 +14,10 @@ type IProps = {
}; };
export default function RoleListContainer(props: IProps) { export default function RoleListContainer(props: IProps) {
const [filteredRoles, setFilteredRoles] = useState<OfficeRole[]>(props.roles);
const router = useRouter(); const router = useRouter();
const { roleUid } = router.query; const { roleUid } = router.query;
const filterRoles = useCallback(
(input: string) => {
const filteredRoles = props.roles.filter((role) => {
return role.name?.toLowerCase().includes(input.toLowerCase());
});
setFilteredRoles(filteredRoles);
},
[props.roles],
);
const onSelectedBlock = useCallback( const onSelectedBlock = useCallback(
(block: IBlock) => { (block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide(); props.onCloseLeftSide && props.onCloseLeftSide();
@ -42,33 +29,25 @@ export default function RoleListContainer(props: IProps) {
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList
<div className={classes["searchbar"]}> blocks={props.roles
<SearchBar onChange={filterRoles} placeholder="Chercher un rôle" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList
blocks={filteredRoles
.filter((role) => { .filter((role) => {
if (role.name === "admin") return false; if (role.name === "admin") return false;
return true; return true;
}) })
.map((role) => { .map((role) => {
return { return {
name: role.name, primaryText: role.name,
id: role.uid!, id: role.uid!,
selected: role.uid === roleUid, isActive: role.uid === roleUid,
}; };
})} })}
onSelectedBlock={onSelectedBlock} onSelectedBlock={onSelectedBlock}
bottomButton={{
link: Module.getInstance().get().modules.pages.Roles.pages.Create.props.path,
text: "Créer un rôle",
}}
/> />
</div> </div>
</div>
<div className={classes["create-container"]}>
<Link href={Module.getInstance().get().modules.pages.Roles.pages.Create.props.path}>
<Button fullwidth={true}>Créer un rôle</Button>
</Link>
</div>
</div>
); );
} }

View File

@ -1,11 +1,11 @@
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import SearchBar from "@Front/Components/DesignSystem/SearchBar";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback, useState } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = { type IProps = {
users: User[]; users: User[];
@ -14,22 +14,9 @@ type IProps = {
}; };
export default function UserListContainer(props: IProps) { export default function UserListContainer(props: IProps) {
const [filteredUsers, setFilteredUsers] = useState<User[]>(props.users);
const router = useRouter(); const router = useRouter();
const { userUid } = router.query; const { userUid } = router.query;
const filterUsers = useCallback(
(input: string) => {
const filteredUsers = props.users.filter((user) => {
return (
user.contact?.first_name?.toLowerCase().includes(input.toLowerCase()) ||
user.contact?.last_name?.toLowerCase().includes(input.toLowerCase())
);
});
setFilteredUsers(filteredUsers);
},
[props.users],
);
const onSelectedBlock = useCallback( const onSelectedBlock = useCallback(
(block: IBlock) => { (block: IBlock) => {
@ -42,23 +29,16 @@ export default function UserListContainer(props: IProps) {
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList
<div className={classes["searchbar"]}> blocks={props.users.map((user) => {
<SearchBar onChange={filterUsers} placeholder="Chercher un utilisateur" />
</div>
<div className={classes["folderlist-container"]}>
<BlockList
blocks={filteredUsers.map((user) => {
return { return {
name: user.contact?.first_name + " " + user.contact?.last_name, primaryText: user.contact?.first_name + " " + user.contact?.last_name,
id: user.uid!, id: user.uid!,
selected: user.uid === userUid, isActive: user.uid === userUid,
}; };
})} })}
onSelectedBlock={onSelectedBlock} onSelectedBlock={onSelectedBlock}
/> />
</div> </div>
</div>
</div>
); );
} }

View File

@ -14,6 +14,7 @@ import NumberPicker from "@Front/Components/Elements/NumberPicker";
import Tabs from "@Front/Components/Elements/Tabs"; import Tabs from "@Front/Components/Elements/Tabs";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate"; import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import useOpenable from "@Front/Hooks/useOpenable"; import useOpenable from "@Front/Hooks/useOpenable";
import Footer from "@Front/Components/DesignSystem/Footer";
import { import {
ArchiveBoxIcon, ArchiveBoxIcon,
ArrowLongLeftIcon, ArrowLongLeftIcon,
@ -797,6 +798,7 @@ export default function DesignSystem() {
variant={EAlertVariant.NEUTRAL} variant={EAlertVariant.NEUTRAL}
/> />
</div> </div>
<Footer />
</div> </div>
</DefaultTemplate> </DefaultTemplate>
); );

View File

@ -24,7 +24,7 @@ export default function DeleteCustomerModal(props: IProps) {
title={"Êtes-vous sûr de vouloir supprimer ce client du dossier ?"} title={"Êtes-vous sûr de vouloir supprimer ce client du dossier ?"}
firstButton={{ children: "Annuler", onClick: onClose }} firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Supprimer le client", onClick: handleDelete }}> secondButton={{ children: "Supprimer le client", onClick: handleDelete }}>
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
Cette action retirera le client de ce dossier. Vous ne pourrez plus récupérer les informations associées à ce client dans ce Cette action retirera le client de ce dossier. Vous ne pourrez plus récupérer les informations associées à ce client dans ce
dossier une fois supprimées. dossier une fois supprimées.
</Typography> </Typography>

View File

@ -131,7 +131,7 @@ export default function ClientBox(props: IProps) {
onClose={closeErrorModal} onClose={closeErrorModal}
title={"Suppression impossible"} title={"Suppression impossible"}
secondButton={{ children: "Fermer", onClick: closeErrorModal, fullwidth: true }}> secondButton={{ children: "Fermer", onClick: closeErrorModal, fullwidth: true }}>
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
Ce client ne peut pas être supprimé car des documents sont associés à son dossier. Veuillez d'abord gérer ou supprimer Ce client ne peut pas être supprimé car des documents sont associés à son dossier. Veuillez d'abord gérer ou supprimer
ces documents avant de tenter de supprimer le client. ces documents avant de tenter de supprimer le client.
</Typography> </Typography>

View File

@ -31,7 +31,7 @@ export default function DeleteAskedDocumentModal(props: IProps) {
title={"Êtes-vous sûr de vouloir supprimer cette demande de document ?"} title={"Êtes-vous sûr de vouloir supprimer cette demande de document ?"}
firstButton={{ children: "Annuler", onClick: onClose }} firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Supprimer la demande", onClick: onDelete }}> secondButton={{ children: "Supprimer la demande", onClick: onDelete }}>
<Typography typo={ETypo.TEXT_MD_light}>Cette action annulera la demande du document en cours.</Typography> <Typography typo={ETypo.TEXT_MD_LIGHT}>Cette action annulera la demande du document en cours.</Typography>
</Modal> </Modal>
); );
} }

View File

@ -14,6 +14,7 @@ import Link from "next/link";
import NoDocument from "./NoDocument"; import NoDocument from "./NoDocument";
import DocumentTables from "./DocumentTables"; import DocumentTables from "./DocumentTables";
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
type IProps = { type IProps = {
folder: OfficeFolder; folder: OfficeFolder;
@ -42,6 +43,9 @@ export default function ClientView(props: IProps) {
label: `${customer.contact?.first_name} ${customer.contact?.last_name}`, label: `${customer.contact?.first_name} ${customer.contact?.last_name}`,
key: customer.uid, key: customer.uid,
value: customer, value: customer,
hasWarning:
customer.documents &&
customer.documents.filter((document) => document.document_status === EDocumentStatus.DEPOSITED).length > 0,
})), })),
[customers], [customers],
); );

View File

@ -34,7 +34,7 @@ export default function DeleteFolderModal(props: IProps) {
title={"Êtes-vous sûr de vouloir supprimer ce dossier ?"} title={"Êtes-vous sûr de vouloir supprimer ce dossier ?"}
firstButton={{ children: "Annuler", onClick: onClose }} firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Supprimer le dossier", onClick: onDelete }}> secondButton={{ children: "Supprimer le dossier", onClick: onDelete }}>
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
Cette action est irréversible. En supprimant ce dossier, toutes les informations associées seront définitivement perdues. Cette action est irréversible. En supprimant ce dossier, toutes les informations associées seront définitivement perdues.
</Typography> </Typography>
</Modal> </Modal>

View File

@ -44,13 +44,13 @@ export default function AnchoringModal(props: IProps) {
firstButton={!isAnchoring ? { children: "Non, Annuler", onClick: onClose } : undefined} firstButton={!isAnchoring ? { children: "Non, Annuler", onClick: onClose } : undefined}
secondButton={!isAnchoring ? { children: "Oui, certifier et ancrer", onClick: anchor } : undefined}> secondButton={!isAnchoring ? { children: "Oui, certifier et ancrer", onClick: anchor } : undefined}>
{!isAnchoring ? ( {!isAnchoring ? (
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
La certification et l'ancrage de ce dossier dans la blockchain sont des actions définitives et garantiront la sécurité La certification et l'ancrage de ce dossier dans la blockchain sont des actions définitives et garantiront la sécurité
et l'authenticité de tous les documents. Veuillez confirmer que vous souhaitez continuer. et l'authenticité de tous les documents. Veuillez confirmer que vous souhaitez continuer.
</Typography> </Typography>
) : ( ) : (
<div className={classes["anchoring"]}> <div className={classes["anchoring"]}>
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
Vos documents sont en train d'être ancrés dans la blockchain. Cela peut prendre quelques instants. Merci de votre Vos documents sont en train d'être ancrés dans la blockchain. Cela peut prendre quelques instants. Merci de votre
patience. patience.
</Typography> </Typography>

View File

@ -37,7 +37,7 @@ export default function ArchiveModal(props: IProps) {
firstButton={{ children: "Annuler", onClick: onClose }} firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Archiver le dossier", onClick: archive }}> secondButton={{ children: "Archiver le dossier", onClick: archive }}>
<div className={classes["root"]}> <div className={classes["root"]}>
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
Archiver ce dossier le déplacera dans la section des dossiers archivés. Vous pouvez ajouter une note de dossier avant Archiver ce dossier le déplacera dans la section des dossiers archivés. Vous pouvez ajouter une note de dossier avant
d'archiver si vous le souhaitez. d'archiver si vous le souhaitez.
</Typography> </Typography>

View File

@ -38,7 +38,7 @@ export default function DownloadAnchoringProofModal(props: IProps) {
title={"Félicitations ! Dossier ancré avec succès"} title={"Félicitations ! Dossier ancré avec succès"}
firstButton={{ children: "Fermer", onClick: onClose }} firstButton={{ children: "Fermer", onClick: onClose }}
secondButton={{ children: "Télécharger la preuve dancrage", onClick: downloadAnchoringProof }}> secondButton={{ children: "Télécharger la preuve dancrage", onClick: downloadAnchoringProof }}>
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
Votre dossier a é validé et ancré dans la blockchain. Vous pouvez maintenant télécharger la preuve d'ancrage pour vos Votre dossier a é validé et ancré dans la blockchain. Vous pouvez maintenant télécharger la preuve d'ancrage pour vos
archives. archives.
</Typography> </Typography>

View File

@ -23,7 +23,7 @@ export default function RequireAnchoringModal(props: IProps) {
title={"Archiver le dossier, action requise : Ancrer et certifier le dossier"} title={"Archiver le dossier, action requise : Ancrer et certifier le dossier"}
firstButton={{ children: "Annuler", onClick: onClose }} firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Ancrer le dossier", onClick: onAnchor }}> secondButton={{ children: "Ancrer le dossier", onClick: onAnchor }}>
<Typography typo={ETypo.TEXT_MD_light}> <Typography typo={ETypo.TEXT_MD_LIGHT}>
Pour archiver ce dossier, il est nécessaire de l'ancrer dans la blockchain afin de garantir la sécurité et l'authenticité Pour archiver ce dossier, il est nécessaire de l'ancrer dans la blockchain afin de garantir la sécurité et l'authenticité
des documents. Veuillez procéder à l'ancrage avant de continuer. des documents. Veuillez procéder à l'ancrage avant de continuer.
</Typography> </Typography>

View File

@ -1,5 +1,4 @@
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
import BlockList, { IBlock } from "@Front/Components/DesignSystem/BlockList";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
@ -9,6 +8,8 @@ import { useCallback, useEffect, useState } from "react";
import LandingImage from "../Login/landing-connect.jpeg"; import LandingImage from "../Login/landing-connect.jpeg";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import BlockList from "@Front/Components/DesignSystem/SearchBlockList/BlockList";
export default function SelectFolder() { export default function SelectFolder() {
const [folders, setFolders] = useState<OfficeFolder[]>([]); const [folders, setFolders] = useState<OfficeFolder[]>([]);
@ -66,7 +67,7 @@ export default function SelectFolder() {
blocks={folders.map((folder) => { blocks={folders.map((folder) => {
return { return {
id: folder.uid!, id: folder.uid!,
name: folder.name!, primaryText: folder.name!,
selected: false, selected: false,
}; };
})} })}

View File

@ -3,7 +3,7 @@ $screen-l: 1439px;
$screen-ls: 1280px; $screen-ls: 1280px;
$screen-m: 1023px; $screen-m: 1023px;
$screen-s: 767px; $screen-s: 767px;
// $screen-xs: 424px; $screen-xs: 424px;
$custom-easing: cubic-bezier(0.645, 0.045, 0.355, 1); $custom-easing: cubic-bezier(0.645, 0.045, 0.355, 1);

View File

@ -1,5 +1,5 @@
@import "@Themes/constants.scss";
@import "@Themes/fonts.scss"; @import "@Themes/fonts.scss";
@import "@Themes/constants.scss";
@import "@Themes/variables.scss"; @import "@Themes/variables.scss";
* { * {

View File

@ -1,6 +1,6 @@
import "@Front/index.scss";
import { DefaultLayout } from "@Front/Components/LayoutTemplates/DefaultLayout"; import { DefaultLayout } from "@Front/Components/LayoutTemplates/DefaultLayout";
import { FrontendVariables } from "@Front/Config/VariablesFront"; import { FrontendVariables } from "@Front/Config/VariablesFront";
import "@Front/index.scss";
import type { NextPage } from "next"; import type { NextPage } from "next";
import type { AppType, AppProps } from "next/app"; import type { AppType, AppProps } from "next/app";
import { useEffect, type ReactElement, type ReactNode } from "react"; import { useEffect, type ReactElement, type ReactNode } from "react";