Merged dev

This commit is contained in:
Vins 2024-07-29 14:32:39 +02:00
commit b71b919f95
91 changed files with 1698 additions and 3012 deletions

View File

@ -6,18 +6,19 @@ import { IOption } from "../Dropdown/DropdownMenu/DropdownOption";
import SearchBar from "../SearchBar"; import SearchBar from "../SearchBar";
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 { getLabel } from "../Dropdown";
type IProps = { type IProps = {
options: IOption[]; options: IOption[];
placeholder?: string; placeholder?: string;
disabled?: boolean; disabled?: boolean;
label?: string; label?: string;
onSelect?: (option: IOption) => void; onSelectionChange?: (option: IOption | null) => void;
selectedOption?: IOption | null; selectedOption?: IOption | null;
}; };
export default function Autocomplete(props: IProps) { export default function Autocomplete(props: IProps) {
const { options, placeholder, disabled, label, selectedOption: selectedOptionProps } = props; const { onSelectionChange, options, placeholder, disabled, label, selectedOption: selectedOptionProps } = props;
const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null); const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null);
const [searchValue, setSearchValue] = useState(""); const [searchValue, setSearchValue] = useState("");
const [filteredOptions, setFilteredOptions] = useState<IOption[]>(options); const [filteredOptions, setFilteredOptions] = useState<IOption[]>(options);
@ -46,29 +47,33 @@ export default function Autocomplete(props: IProps) {
[openable], [openable],
); );
const handleChange = useCallback(
(option: IOption | null) => {
setSelectedOption(option);
onSelectionChange?.(option);
},
[onSelectionChange],
);
useEffect(() => { useEffect(() => {
setSelectedOption(selectedOptionProps ?? null); setSelectedOption(selectedOptionProps ?? null);
}, [selectedOptionProps]); }, [selectedOptionProps]);
const handleSelectOption = useCallback( const handleSelectOption = useCallback(
(option: IOption) => { (newOption: IOption, _options: IOption[]) => {
setSelectedOption(option); handleChange(newOption);
setSearchValue(getLabel(option) || ""); setSearchValue(getLabel(newOption) || "");
openable.close(); openable.close();
}, },
[openable], [handleChange, openable],
); );
function getLabel(option: IOption | null): string | null {
if (!option) return null;
if (typeof option.label === "string") {
return option.label;
}
return `${option.label.text} ${option.label.subtext}`;
}
return ( return (
<DropdownMenu options={filteredOptions} openable={openable} onSelect={handleSelectOption} selectedOption={selectedOption}> <DropdownMenu
options={filteredOptions}
openable={openable}
onSelect={handleSelectOption}
selectedOptions={selectedOption ? [selectedOption] : []}>
<div className={classes["root"]}> <div className={classes["root"]}>
{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}>
@ -76,7 +81,14 @@ export default function Autocomplete(props: IProps) {
</Typography> </Typography>
)} )}
</div> </div>
<SearchBar placeholder={placeholder} disabled={disabled} onChange={handleSearchChange} value={searchValue} /> <SearchBar
placeholder={placeholder}
disabled={disabled}
onChange={handleSearchChange}
value={searchValue}
onClear={() => handleChange(null)}
onFocus={openable.open}
/>
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -0,0 +1,20 @@
@import "@Themes/constants.scss";
.root {
width: fit-content;
height: 32px;
display: inline-flex;
padding: 4px 12px;
align-items: center;
gap: 8px;
border-radius: var(--input-chip-radius, 360px);
border: 1px solid var(--input-chip-default-border, #b7d1f1);
background: var(--input-chip-default-background, #e5eefa);
&:hover {
background-color: var(--input-chip-hovered-background);
border-color: var(--input-chip-hovered-border);
}
}

View File

@ -0,0 +1,26 @@
import { XMarkIcon } from "@heroicons/react/24/outline";
import classNames from "classnames";
import React from "react";
import IconButton from "../../IconButton";
import Typography, { ETypo, ETypoColor } from "../../Typography";
import classes from "./classes.module.scss";
type IProps = {
text: string;
className?: string;
onDelete?: () => void;
};
export default function Chip(props: IProps) {
const { className, text, onDelete } = props;
return (
<div className={classNames(classes["root"], className)}>
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.INPUT_CHIP_CONTRAST}>
{text}
</Typography>
<IconButton icon={<XMarkIcon />} onClick={onDelete} />
</div>
);
}

View File

@ -0,0 +1,73 @@
@import "@Themes/constants.scss";
.root {
border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-filled, #6d7e8a);
background: var(--input-background, #fff);
svg {
stroke: var(--button-icon-button-default-default);
}
&:hover {
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);
}
&[data-is-disabled="true"] {
opacity: var(--opacity-disabled, 0.3);
pointer-events: none;
}
.content {
display: flex;
align-items: center;
align-content: center;
gap: 16px var(--spacing-2, 16px);
flex-wrap: wrap;
min-height: 56px;
padding: var(--spacing-1-5, 12px) var(--spacing-sm, 8px);
.input {
flex: 1;
border: none;
color: var(--input-placeholder-filled, #24282e);
/* text/md/semibold */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-semibold, 600);
line-height: normal;
letter-spacing: 0.08px;
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;
}
}
}
}

View File

@ -0,0 +1,90 @@
import React, { useCallback, useEffect } from "react";
import { getLabel } from "../../Dropdown";
import { IOption } from "../../Dropdown/DropdownMenu/DropdownOption";
import Chip from "../Chip";
import classes from "./classes.module.scss";
type IProps = {
selectedOptions: IOption[];
onSelectedOptionsChange: (options: IOption[]) => void;
onChange?: (input: string) => void;
value?: string;
placeholder?: string;
disabled?: boolean;
onClear?: () => void;
onFocus?: () => void;
onBlur?: () => void;
};
export default function ChipContainer(props: IProps) {
const {
selectedOptions,
onChange,
value: propValue,
placeholder = "Rechercher",
disabled = false,
onFocus,
onBlur,
onSelectedOptionsChange,
} = props;
const [isFocused, setIsFocused] = React.useState(false);
const [value, setValue] = React.useState(propValue || "");
const changeValue = useCallback(
(value: string) => {
setValue(value);
onChange && onChange(value);
},
[onChange],
);
const handleOnChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value), [changeValue]);
const handleFocus = useCallback(() => {
setIsFocused(true);
onFocus?.();
}, [onFocus]);
const handleBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement, Element>) => {
setIsFocused(false);
onBlur?.();
},
[onBlur],
);
const onChipDelete = useCallback(
(option: IOption) => {
const newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.id !== option.id);
onSelectedOptionsChange && onSelectedOptionsChange(newSelectedOptions);
},
[selectedOptions, onSelectedOptionsChange],
);
useEffect(() => {
if (propValue !== undefined) {
setValue(propValue);
}
}, [propValue]);
return (
<div className={classes["root"]} data-is-focused={isFocused} data-has-value={value !== ""} data-is-disabled={disabled}>
<div className={classes["content"]}>
{selectedOptions.map((option) => (
<Chip key={option.id} text={getLabel(option) ?? ""} onDelete={() => onChipDelete(option)} />
))}
<input
type="text"
value={value}
placeholder={placeholder}
className={classes["input"]}
onChange={handleOnChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,7 @@
@import "@Themes/constants.scss";
.root {
.label {
padding: 0px var(--spacing-2, 16px);
}
}

View File

@ -0,0 +1,95 @@
import useOpenable from "@Front/Hooks/useOpenable";
import { useCallback, useEffect, useState } from "react";
import { getLabel } from "../Dropdown";
import DropdownMenu from "../Dropdown/DropdownMenu";
import { IOption } from "../Dropdown/DropdownMenu/DropdownOption";
import Typography, { ETypo, ETypoColor } from "../Typography";
import ChipContainer from "./ChipContainer";
import classes from "./classes.module.scss";
type IProps = {
options: IOption[];
placeholder?: string;
disabled?: boolean;
label?: string;
onSelectionChange?: (options: IOption[] | null) => void;
selectedOptions?: IOption[] | null;
};
export default function AutocompleteMultiSelect(props: IProps) {
const { onSelectionChange, options, placeholder, disabled, label, selectedOptions: selectedOptionsProps } = props;
const [selectedOptions, setSelectedOptions] = useState<IOption[] | null>(selectedOptionsProps ?? null);
const [searchValue, setSearchValue] = useState("");
const [filteredOptions, setFilteredOptions] = useState<IOption[]>(options);
const openable = useOpenable({ defaultOpen: false });
useEffect(() => {
if (searchValue) {
const filteredOptions = options.filter((option) => getLabel(option)?.toLowerCase().includes(searchValue.toLowerCase()));
if (filteredOptions.length === 0)
return setFilteredOptions([{ id: "no-results", label: "Aucun résulats", notSelectable: true }]);
return setFilteredOptions(filteredOptions);
}
return setFilteredOptions(options);
}, [searchValue, options]);
const handleSearchChange = useCallback(
(value: string) => {
setSearchValue(value);
if (value) {
openable.open();
} else {
openable.close();
}
},
[openable],
);
const handleChange = useCallback(
(options: IOption[] | null) => {
setSelectedOptions(options);
onSelectionChange?.(options);
},
[onSelectionChange],
);
useEffect(() => {
setSelectedOptions(selectedOptionsProps ?? null);
}, [selectedOptionsProps]);
const handleSelectOption = useCallback(
(_newOption: IOption, options: IOption[]) => {
handleChange(options);
setSearchValue("");
openable.close();
},
[handleChange, openable],
);
return (
<DropdownMenu
options={filteredOptions}
openable={openable}
onSelect={handleSelectOption}
selectedOptions={selectedOptions ? selectedOptions : []}>
<div className={classes["root"]}>
{label && (
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
{label}
</Typography>
)}
</div>
<ChipContainer
placeholder={placeholder}
disabled={disabled}
onChange={handleSearchChange}
value={searchValue}
onClear={() => handleChange(null)}
onFocus={openable.open}
selectedOptions={selectedOptions ?? []}
onSelectedOptionsChange={handleChange}
/>
</DropdownMenu>
);
}

View File

@ -30,6 +30,7 @@
overflow: visible; overflow: visible;
.content { .content {
max-height: 500px; max-height: 500px;
overflow: auto;
opacity: 1; opacity: 1;
} }
} }

View File

@ -1,12 +1,12 @@
import classNames from "classnames"; import classNames from "classnames";
import React, { useCallback } from "react"; import React, { useCallback, useEffect, useRef } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import DropdownOption, { IOption } from "./DropdownOption"; import DropdownOption, { IOption } from "./DropdownOption";
type IProps = { type IProps = {
options: IOption[]; options: IOption[];
selectedOption: IOption | null; selectedOptions: IOption[];
children: React.ReactNode; children: React.ReactNode;
openable: { openable: {
isOpen: boolean; isOpen: boolean;
@ -14,21 +14,40 @@ type IProps = {
close: () => void; close: () => void;
toggle: () => void; toggle: () => void;
}; };
onSelect?: (option: IOption) => void; onSelect?: (newOption: IOption, options: IOption[]) => void;
}; };
export default function DropdownMenu(props: IProps) { export default function DropdownMenu(props: IProps) {
const { children, options, onSelect, openable, selectedOption } = props; const { children, options, onSelect, openable, selectedOptions } = props;
const ref = useRef<HTMLDivElement>(null);
const handleSelect = useCallback( const handleSelect = useCallback(
(option: IOption) => { (option: IOption) => {
onSelect?.(option); const newOptions = selectedOptions.some((selectedOption) => selectedOption.id === option.id)
? selectedOptions
: [...selectedOptions, option];
onSelect?.(option, newOptions);
openable.close(); openable.close();
}, },
[onSelect, openable], [onSelect, openable, selectedOptions],
); );
const handleClickOutside = useCallback(
(event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
openable.close();
}
},
[openable],
);
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [handleClickOutside]);
return ( return (
<div className={classNames([classes["root"], openable.isOpen && classes["open"]])}> <div className={classNames([classes["root"], openable.isOpen && classes["open"]])} ref={ref}>
{children} {children}
<div className={classes["content"]}> <div className={classes["content"]}>
{options.map((option) => { {options.map((option) => {
@ -39,6 +58,6 @@ export default function DropdownMenu(props: IProps) {
); );
function isActive(option: IOption): boolean { function isActive(option: IOption): boolean {
return selectedOption?.id === option.id; return selectedOptions.some((selectedOption) => selectedOption.id === option.id);
} }
} }

View File

@ -30,6 +30,7 @@
padding: 0px var(--spacing-2, 16px); padding: 0px var(--spacing-2, 16px);
align-items: center; align-items: center;
flex: 1 0 0; flex: 1 0 0;
gap: 4px;
} }
svg { svg {

View File

@ -13,12 +13,12 @@ type IProps = {
label?: string; label?: string;
placeholder?: string; placeholder?: string;
disabled?: boolean; disabled?: boolean;
onSelect?: (option: IOption) => void; onSelectionChange?: (option: IOption) => void;
selectedOption?: IOption | null; selectedOption?: IOption | null;
}; };
export default function Dropdown(props: IProps) { export default function Dropdown(props: IProps) {
const { options, placeholder, disabled, onSelect, selectedOption: selectedOptionProps, label } = props; const { options, placeholder, disabled, onSelectionChange, selectedOption: selectedOptionProps, label } = 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 });
@ -27,15 +27,19 @@ export default function Dropdown(props: IProps) {
}, [selectedOptionProps]); }, [selectedOptionProps]);
const handleOnSelect = useCallback( const handleOnSelect = useCallback(
(option: IOption) => { (newOption: IOption, _options: IOption[]) => {
setSelectedOption(option); setSelectedOption(newOption);
onSelect?.(option); onSelectionChange?.(newOption);
}, },
[onSelect], [onSelectionChange],
); );
return ( return (
<DropdownMenu options={options} openable={openable} onSelect={handleOnSelect} selectedOption={selectedOption}> <DropdownMenu
options={options}
openable={openable}
onSelect={handleOnSelect}
selectedOptions={selectedOption ? [selectedOption] : []}>
<div className={classes["root"]}> <div className={classes["root"]}>
{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}>
@ -51,12 +55,7 @@ export default function Dropdown(props: IProps) {
])} ])}
onClick={openable.toggle}> onClick={openable.toggle}>
<div className={classes["content"]}> <div className={classes["content"]}>
<Typography {getLabelContent(selectedOption, placeholder)}
className={classes["value"]}
typo={!!selectedOption ? ETypo.TEXT_MD_SEMIBOLD : ETypo.TEXT_MD_REGULAR}
color={!!selectedOption ? ETypoColor.DROPDOWN_CONTRAST_ACTIVE : ETypoColor.DROPDOWN_CONTRAST_DEFAULT}>
{getLabel(selectedOption) ?? placeholder}
</Typography>
<ChevronDownIcon /> <ChevronDownIcon />
</div> </div>
</div> </div>
@ -70,5 +69,44 @@ export function getLabel(option: IOption | null): string | null {
if (typeof option.label === "string") { if (typeof option.label === "string") {
return option.label; return option.label;
} }
return `${option.label.text} ${option.label.subtext}`; return `${option.label.text}, ${option.label.subtext}`;
}
function getLabelContent(option: IOption | null, placeholder?: string) {
if (!option)
return (
<Typography
className={classes["value"]}
typo={!!option ? ETypo.TEXT_MD_SEMIBOLD : ETypo.TEXT_MD_REGULAR}
color={!!option ? ETypoColor.DROPDOWN_CONTRAST_ACTIVE : ETypoColor.DROPDOWN_CONTRAST_DEFAULT}>
{placeholder}
</Typography>
);
if (typeof option.label === "string") {
return (
<Typography
className={classes["value"]}
typo={!!option ? ETypo.TEXT_MD_SEMIBOLD : ETypo.TEXT_MD_REGULAR}
color={!!option ? ETypoColor.DROPDOWN_CONTRAST_ACTIVE : ETypoColor.DROPDOWN_CONTRAST_DEFAULT}>
{option.label}
</Typography>
);
}
return (
<span className={classes["value"]}>
<Typography
typo={ETypo.TEXT_MD_LIGHT}
color={!!option ? ETypoColor.NAVIGATION_BUTTON_CONTRAST_ACTIVE : ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{`${option.label.text} , `}
</Typography>
<Typography
typo={ETypo.TEXT_MD_BOLD}
type="span"
color={!!option ? ETypoColor.NAVIGATION_BUTTON_CONTRAST_ACTIVE : ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{option.label.subtext}
</Typography>
</span>
);
} }

View File

@ -1,14 +0,0 @@
@import "@Themes/constants.scss";
.root {
height: calc(100vh - 290px);
overflow-y: scroll;
&.archived {
height: calc(100vh - 220px);
}
.active {
background-color: var(--color-neutral-200);
}
}

View File

@ -1,76 +0,0 @@
import Module from "@Front/Config/Module";
import classNames from "classnames";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import FolderContainer from "../FolderContainer";
import classes from "./classes.module.scss";
type IProps = {
folders: OfficeFolder[];
isArchived: boolean;
onSelectedFolder?: (folder: OfficeFolder) => void;
onCloseLeftSide?: () => void;
};
type IPropsClass = IProps & {
selectedFolder: string;
};
type IState = {};
class FolderListClass 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.FolderInformation.props.path;
public override render(): JSX.Element {
return <div className={classNames(classes["root"], this.props.isArchived ? classes["archived"] : "")}>{this.renderFolders()}</div>;
}
private renderFolders(): JSX.Element[] {
const pendingFolders = this.props.folders
.filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED,
);
return pendingDocuments.length >= 1;
})
.sort((folder1, folder2) => {
return folder1.created_at! > folder2.created_at! ? -1 : 1;
});
const otherFolders = this.props.folders
.filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED,
);
return pendingDocuments.length === 0;
})
.sort((folder1, folder2) => {
return folder1.created_at! > folder2.created_at! ? -1 : 1;
});
return [...pendingFolders, ...otherFolders].map((folder) => {
return (
<div
onClick={this.props.onCloseLeftSide}
key={folder.uid}
className={folder.uid === this.props.selectedFolder ? classes["active"] : ""}>
<Link href={this.redirectPath.replace("[folderUid]", folder.uid ?? "")}>
<FolderContainer folder={folder} onSelectedFolder={this.props.onSelectedFolder} />;
</Link>
</div>
);
});
}
}
export default function FolderList(props: IProps) {
const router = useRouter();
let { folderUid } = router.query;
folderUid = folderUid as string;
return <FolderListClass {...props} selectedFolder={folderUid} />;
}

View File

@ -1,7 +0,0 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
justify-content: space-between;
}

View File

@ -1,91 +0,0 @@
import Module from "@Front/Config/Module";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import { useRouter } from "next/router";
import React, { useCallback, useEffect } from "react";
import classes from "./classes.module.scss";
import { IBlock } from "../SearchBlockList/BlockList/Block";
import SearchBlockList from "../SearchBlockList";
type IProps = {
folders: OfficeFolder[];
isArchived: boolean;
onSelectedFolder?: (folder: OfficeFolder) => void;
onCloseLeftSide?: () => void;
};
export default function FolderListContainer(props: IProps) {
const router = useRouter();
const { folderUid } = router.query;
const { folders, isArchived } = props;
const redirectPath: string = isArchived
? Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.pages.FolderInformation.props.path
: Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path;
const getBlocks = useCallback(
(folders: OfficeFolder[]): IBlock[] => {
const pendingFolders = folders
.filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED,
);
return pendingDocuments.length >= 1;
})
.sort((folder1, folder2) => {
return folder1.created_at! > folder2.created_at! ? -1 : 1;
});
const otherFolders = folders
.filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED,
);
return pendingDocuments.length === 0;
})
.sort((folder1, folder2) => {
return folder1.created_at! > folder2.created_at! ? -1 : 1;
});
return [...pendingFolders, ...otherFolders].map((folder) => {
return {
id: folder.uid!,
primaryText: folder.name,
secondaryText: folder.folder_number,
isActive: folderUid === folder.uid,
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
};
});
},
[folderUid],
);
const [blocks, setBlocks] = React.useState<IBlock[]>(getBlocks(folders));
const onSelectedFolder = (block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const folder = folders.find((folder) => folder.uid === block.id);
if (!folder) return;
props.onSelectedFolder && props.onSelectedFolder(folder);
const path = redirectPath.replace("[folderUid]", folder.uid ?? "");
router.push(path);
};
useEffect(() => {
setBlocks(getBlocks(folders));
}, [folders, getBlocks]);
return (
<div className={classes["root"]}>
<SearchBlockList
blocks={blocks}
onSelectedBlock={onSelectedFolder}
bottomButton={{
link: Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path,
text: "Créer un dossier",
}}
/>
</div>
);
}

View File

@ -14,7 +14,7 @@
align-items: center; align-items: center;
gap: var(--Radius-lg, 16px); gap: var(--Radius-lg, 16px);
white-space: nowrap; white-space: nowrap;
padding: 0 360px; padding: var(--spacing-1-5, 12px) var(--Radius-xl, 24px);
//make it sticky //make it sticky
@media (max-width: 1023px) { @media (max-width: 1023px) {
@ -22,7 +22,18 @@
} }
} }
@media (max-width: 660px) or (min-width: 768px) { &[data-has-left-padding="true"] {
.desktop {
padding: var(--spacing-1-5, 12px) var(--spacing-15, 120px);
}
}
&[data-is-sticky="true"] {
position: sticky;
bottom: 0;
}
@media (max-width: 660px) or (min-width: 769px) {
.tablet { .tablet {
display: none; display: none;
} }

View File

@ -1,20 +1,25 @@
import React from "react"; import React from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Link from "next/link";
import Module from "@Front/Config/Module";
import { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
type IProps = { type IProps = {
className?: string; className?: string;
}; };
const legalPages = Module.getInstance().get().modules.pages.Legal.pages.LegalInformations.props.path;
export default function Desktop({ className }: IProps) { export default function Desktop({ className }: IProps) {
return ( return (
<div className={[classes["sub-root"], className].join(" ")}> <div className={[classes["sub-root"], className].join(" ")}>
<span>© Copyright lecoffre 2024</span> <Link href={legalPages.replace("[legalUid]", ELegalOptions.LEGAL_MENTIONS)}>© Copyright lecoffre 2024</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/terms">Conditions d'utilisation</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.CGU)}>Conditions d'utilisation</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/privacy">Politique de confidentialité</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.POLITIQUE_DE_CONFIDENTIALITE)}>Politique de confidentialité</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/cookies">Politique des cookies</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES)}>Politique des cookies</Link>
</div> </div>
); );
} }

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Mobile from "./mobile"; import Mobile from "./mobile";
import Desktop from "./desktop"; import Desktop from "./desktop";
@ -6,11 +6,23 @@ import Tablet from "./tablet";
type IProps = { type IProps = {
className?: string; className?: string;
hasLeftPadding?: boolean;
isSticky?: boolean;
}; };
export default function Footer({ className }: IProps) { export default function Footer({ className, hasLeftPadding = false, isSticky = false }: IProps) {
const footerRef = React.useRef<HTMLDivElement>(null);
useEffect(() => {
if (!footerRef.current) return;
const footerHeight = footerRef.current.clientHeight + 1;
document.documentElement.style.setProperty("--footer-height", `${footerHeight}px`);
});
return ( return (
<footer className={[classes["root"], className].join(" ")}> <footer
className={[classes["root"], className].join(" ")}
data-has-left-padding={hasLeftPadding}
data-is-sticky={isSticky}
ref={footerRef}>
<Mobile className={classes["mobile"]} /> <Mobile className={classes["mobile"]} />
<Tablet className={classes["tablet"]} /> <Tablet className={classes["tablet"]} />
<Desktop className={classes["desktop"]} /> <Desktop className={classes["desktop"]} />

View File

@ -1,18 +1,23 @@
import React from "react"; import React from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Link from "next/link";
import Module from "@Front/Config/Module";
import { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
type IProps = { type IProps = {
className?: string; className?: string;
}; };
const legalPages = Module.getInstance().get().modules.pages.Legal.pages.LegalInformations.props.path;
export default function Mobile({ className }: IProps) { export default function Mobile({ className }: IProps) {
return ( return (
<div className={[classes["sub-root"], className].join(" ")}> <div className={[classes["sub-root"], className].join(" ")}>
<span>© Lecoffre 2024</span> <Link href={legalPages.replace("[legalUid]", ELegalOptions.LEGAL_MENTIONS)}>© Lecoffre 2024</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/terms">Juridiques</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.LEGAL_MENTIONS)}>Juridiques</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/cookies">Cookies</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES)}>Cookies</Link>
</div> </div>
); );
} }

View File

@ -1,20 +1,24 @@
import React from "react"; import React from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Module from "@Front/Config/Module";
import { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
import Link from "next/link";
type IProps = { type IProps = {
className?: string; className?: string;
}; };
const legalPages = Module.getInstance().get().modules.pages.Legal.pages.LegalInformations.props.path;
export default function Tablet({ className }: IProps) { export default function Tablet({ className }: IProps) {
return ( return (
<div className={[classes["sub-root"], className].join(" ")}> <div className={[classes["sub-root"], className].join(" ")}>
<span>© Lecoffre 2024</span> <Link href={legalPages.replace("[legalUid]", ELegalOptions.LEGAL_MENTIONS)}>© Lecoffre 2024</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/terms">Conditions d'utilisation</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.CGU)}>Conditions d'utilisation</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/privacy">Politique de confidentialité</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.POLITIQUE_DE_CONFIDENTIALITE)}>Politique de confidentialité</Link>
<span className={classes["separator"]} /> <span className={classes["separator"]} />
<a href="/cookies">Politique des cookies</a> <Link href={legalPages.replace("[legalUid]", ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES)}>Politique des cookies</Link>
</div> </div>
); );
} }

View File

@ -7,7 +7,7 @@ import BaseField, { IProps as IBaseFieldProps, IState as IBaseFieldState } from
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
export type IProps = IBaseFieldProps & { export type IProps = IBaseFieldProps & {
onSelect?: (option: IOption) => void; onSelectionChange?: (option: IOption | null) => void;
options: IOption[]; options: IOption[];
selectedOption?: IOption | null; selectedOption?: IOption | null;
label?: string; label?: string;
@ -28,9 +28,9 @@ export default class AutocompleteField extends BaseField<IProps, IState> {
this.handleOnChange = this.handleOnChange.bind(this); this.handleOnChange = this.handleOnChange.bind(this);
} }
private handleOnChange = (option: IOption) => { private handleOnChange = (option: IOption | null) => {
this.setState({ selectedOption: option }); this.setState({ selectedOption: option });
this.props.onSelect?.(option); this.props.onSelectionChange?.(option);
}; };
public override componentDidUpdate(prevProps: IProps): void { public override componentDidUpdate(prevProps: IProps): void {
@ -45,9 +45,10 @@ export default class AutocompleteField extends BaseField<IProps, IState> {
<Autocomplete <Autocomplete
options={this.props.options} options={this.props.options}
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
onSelect={this.handleOnChange} onSelectionChange={this.handleOnChange}
selectedOption={this.state.selectedOption} selectedOption={this.state.selectedOption}
label={this.props.label} label={this.props.label}
disabled={this.props.disabled}
/> />
{this.state.selectedOption && ( {this.state.selectedOption && (
<input <input

View File

@ -0,0 +1,11 @@
@import "@Themes/constants.scss";
.root {
.hidden-input {
position: absolute;
opacity: 0;
}
.errors-container {
margin-top: 8px;
}
}

View File

@ -0,0 +1,66 @@
import React from "react";
import { ReactNode } from "react";
import { IOption } from "../../Dropdown/DropdownMenu/DropdownOption";
import BaseField, { IProps as IBaseFieldProps, IState as IBaseFieldState } from "../BaseField";
import classes from "./classes.module.scss";
import AutocompleteMultiSelect from "../../AutocompleteMultiSelect";
export type IProps = IBaseFieldProps & {
onSelectionChange?: (options: IOption[] | null) => void;
options: IOption[];
selectedOptions?: IOption[] | null;
label?: string;
};
type IState = IBaseFieldState & {
selectedOptions: IOption[] | null;
};
export default class AutocompleteMultiSelectField extends BaseField<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
selectedOptions: this.props.selectedOptions ?? null,
...this.getDefaultState(),
};
this.handleOnChange = this.handleOnChange.bind(this);
}
private handleOnChange = (options: IOption[] | null) => {
this.setState({ selectedOptions: options });
this.props.onSelectionChange?.(options);
};
public override componentDidUpdate(prevProps: IProps): void {
if (prevProps.selectedOptions !== this.props.selectedOptions) {
this.setState({ selectedOptions: this.props.selectedOptions ?? null });
}
}
public override render(): ReactNode {
return (
<div className={classes["root"]}>
<AutocompleteMultiSelect
options={this.props.options}
placeholder={this.props.placeholder}
onSelectionChange={this.handleOnChange}
selectedOptions={this.state.selectedOptions}
label={this.props.label}
disabled={this.props.disabled}
/>
{this.state.selectedOptions && (
<input
className={classes["hidden-input"]}
name={this.props.name}
type="text"
defaultValue={JSON.stringify(this.state.selectedOptions)}
hidden
/>
)}
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</div>
);
}
}

View File

@ -7,7 +7,7 @@ import classes from "./classes.module.scss";
import Dropdown from "../../Dropdown"; import Dropdown from "../../Dropdown";
export type IProps = IBaseFieldProps & { export type IProps = IBaseFieldProps & {
onSelect?: (option: IOption) => void; onSelectionChange?: (option: IOption) => void;
options: IOption[]; options: IOption[];
selectedOption?: IOption | null; selectedOption?: IOption | null;
label?: string; label?: string;
@ -30,7 +30,7 @@ export default class SelectField extends BaseField<IProps, IState> {
private handleOnChange = (option: IOption) => { private handleOnChange = (option: IOption) => {
this.setState({ selectedOption: option }); this.setState({ selectedOption: option });
this.props.onSelect?.(option); this.props.onSelectionChange?.(option);
}; };
public override componentDidUpdate(prevProps: IProps): void { public override componentDidUpdate(prevProps: IProps): void {
@ -45,9 +45,10 @@ export default class SelectField extends BaseField<IProps, IState> {
<Dropdown <Dropdown
options={this.props.options} options={this.props.options}
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
onSelect={this.handleOnChange} onSelectionChange={this.handleOnChange}
selectedOption={this.state.selectedOption} selectedOption={this.state.selectedOption}
label={this.props.label} label={this.props.label}
disabled={this.props.disabled}
/> />
{this.state.selectedOption && ( {this.state.selectedOption && (
<input <input

View File

@ -94,6 +94,10 @@
} }
} }
&.default {
padding: 0;
}
&.disabled { &.disabled {
cursor: default; cursor: default;
opacity: var(--opacity-disabled, 0.3); opacity: var(--opacity-disabled, 0.3);

View File

@ -1,79 +0,0 @@
@import "@Themes/constants.scss";
.root {
.label-container {
cursor: pointer;
display: flex;
flex-direction: column;
flex: 1;
position: relative;
border: 1px solid var(--color-neutral-200);
background-color: transparent;
.placeholder {
position: absolute;
top: 24px;
left: 24px;
pointer-events: none;
display: flex;
align-items: center;
transition: top 0.3s ease-in-out;
background-color: white;
padding: 0 4px;
&[data-selected="true"] {
top: -12px;
}
}
.label {
font-family: var(--font-text-family);
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 18px;
text-transform: uppercase;
color: var(--color-primary-8);
pointer-events: none;
}
.input-container {
display: flex;
outline: none;
gap: 16px;
border: none;
width: 100%;
> div {
width: 100%;
> div:first-of-type > div:first-of-type {
padding: 24px;
}
}
}
&.active {
.input-container {
> div > div > div {
padding: 24px 16px 16px 16px !important;
}
}
}
.is-active-placeholder {
position: absolute;
top: -11px;
left: 8px;
background-color: #ffffff;
z-index: 1;
padding: 0 16px;
}
&[data-is-errored="true"] {
.input {
border: 1px solid var(--color-error-600);
~ .fake-placeholder {
color: var(--color-error-600);
}
}
}
}
}

View File

@ -1,158 +0,0 @@
import { ValidationError } from "class-validator";
import classNames from "classnames";
import React from "react";
import ReactSelect, { ActionMeta, MultiValue, Options, PropsValue } from "react-select";
import { IOptionOld } from "../Form/SelectFieldOld";
import Typography, { ETypo, ETypoColor } from "../Typography";
import classes from "./classes.module.scss";
import { styles } from "./styles";
type IProps = {
options: IOptionOld[];
label?: string | JSX.Element;
placeholder?: string;
onChange?: (newValue: MultiValue<IOptionOld>, actionMeta: ActionMeta<IOptionOld>) => void;
defaultValue?: PropsValue<IOptionOld>;
value?: PropsValue<IOptionOld>;
isMulti?: boolean;
shouldCloseMenuOnSelect: boolean;
isOptionDisabled?: (option: IOptionOld, selectValue: Options<IOptionOld>) => boolean;
validationError?: ValidationError;
};
type IState = {
isFocused: boolean;
selectedOptions: MultiValue<IOptionOld>;
validationError: ValidationError | null;
};
export default class MultiSelect extends React.Component<IProps, IState> {
public static defaultProps: Partial<IProps> = {
placeholder: "Sélectionner une option...",
shouldCloseMenuOnSelect: false,
};
constructor(props: IProps) {
super(props);
this.state = {
isFocused: false,
selectedOptions: [],
validationError: this.props.validationError ?? null,
};
this.hasError = this.hasError.bind(this);
this.onChange = this.onChange.bind(this);
this.onEmptyResearch = this.onEmptyResearch.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
this.renderErrors = this.renderErrors.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<div className={classNames(classes["label-container"], this.state.selectedOptions.length >= 1 && classes["active"])}>
{this.props.label && <div className={classes["label"]}>{this.props.label}</div>}
{this.props.placeholder && (
<div
className={classes["placeholder"]}
data-selected={(this.state.isFocused || this.state.selectedOptions.length >= 1).toString()}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
{this.props.placeholder}
</Typography>
</div>
)}
<div className={classes["input-container"]}>
<ReactSelect
placeholder={""}
options={this.props.options}
styles={styles}
onChange={this.onChange}
value={this.props.defaultValue}
defaultValue={this.state.selectedOptions}
closeMenuOnSelect={this.props.shouldCloseMenuOnSelect}
isMulti
isOptionDisabled={this.props.isOptionDisabled}
noOptionsMessage={this.onEmptyResearch}
onFocus={this.onFocus}
onBlur={this.onBlur}
classNamePrefix="react-select"
/>
</div>
</div>
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</div>
);
}
public override componentDidMount(): void {
if (this.props.defaultValue) {
// If default value contains multiple IOptions
if (Array.isArray(this.props.defaultValue)) {
this.setState({ selectedOptions: this.props.defaultValue });
}
// If default value is a single IOption
if ("label" in this.props.defaultValue) {
this.setState({ selectedOptions: [this.props.defaultValue] });
}
}
}
public override componentDidUpdate(prevProps: IProps): void {
if (this.props.validationError !== prevProps.validationError) {
this.setState({
validationError: this.props.validationError ?? null,
});
}
if (this.props.defaultValue && this.props.defaultValue !== prevProps.defaultValue) {
// If default value contains multiple IOptions
if (Array.isArray(this.props.defaultValue)) {
this.setState({ selectedOptions: this.props.defaultValue });
}
// If default value is a single IOption
if ("label" in this.props.defaultValue) {
this.setState({ selectedOptions: [this.props.defaultValue] });
}
}
}
private onFocus() {
this.setState({ isFocused: true });
}
private onBlur() {
this.setState({ isFocused: false });
}
private onChange(newValue: MultiValue<IOptionOld>, actionMeta: ActionMeta<IOptionOld>) {
this.props.onChange && this.props.onChange(newValue, actionMeta);
this.setState({
selectedOptions: newValue,
validationError: null,
});
}
private onEmptyResearch() {
if (this.state.selectedOptions.length === this.props.options.length) {
return null;
}
return "Aucune option trouvée";
}
protected hasError(): boolean {
return this.state.validationError !== null;
}
protected renderErrors(): JSX.Element[] | null {
if (!this.state.validationError || !this.state.validationError.constraints) return null;
let errors: JSX.Element[] = [];
Object.entries(this.state.validationError.constraints).forEach(([key, value]) => {
errors.push(
<Typography key={key} typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_ERROR_600}>
{value}
</Typography>,
);
});
return errors;
}
}

View File

@ -1,100 +0,0 @@
export const styles = {
option: (provided: any, props: { isSelected: boolean; isFocused: boolean }) => ({
...provided,
cursor: "pointer",
padding: "8px 24px",
fontFamily: "var(--font-text-family)",
fontStyle: "normal",
fontWeight: "400",
fontSize: "18px",
lineHeight: "21.78px",
color: "#939393",
backgroundColor: props.isSelected ? "var(--color-primary-3)" : props.isFocused ? "var(--color-primary-3)" : undefined,
":active": {
...provided[":active"],
backgroundColor: props.isSelected ? "var(--color-primary-3)" : undefined,
},
}),
control: () => ({
width: "100%",
display: "flex",
background: "transparent",
}),
valueContainer: (provided: any) => ({
...provided,
padding: 0,
minWidth: "100px",
fontFamily: "var(--font-text-family)",
fontStyle: "normal",
fontWeight: "600",
fontSize: "16px",
lineHeight: "22px",
color: "#939393",
letter: "0.5 px",
}),
multiValue: (provided: any) => ({
...provided,
margin: "4px",
padding: "8px 16px",
fontStyle: "normal",
fontWeight: "400",
fontSize: "16px",
lineHeight: "22px",
background: "transparent",
border: "1px solid black",
borderRadius: "100px",
}),
multiValueLabel: (provided: any) => ({
...provided,
fontSize: "16px",
color: "#939393",
fontWeight: "400",
}),
input: (provided: any) => ({
...provided,
margin: 0,
padding: 0,
}),
placeholder: (provided: any) => ({
...provided,
fontSize: "16px",
lineHeight: "22px",
fontWeight: "400",
color: "#939393",
}),
indicatorSeparator: () => ({
display: "none",
}),
menu: (provided: any) => ({
...provided,
position: "static",
border: "0",
boxShadow: "none",
}),
menuList: (provided: any) => ({
...provided,
}),
multiValueRemove: (provided: any) => ({
...provided,
backgroundColor: "transparent",
color: "black",
"&:hover": {
color: "grey",
backgroundColor: "transparent",
},
">svg": {
width: "20px",
height: "20px",
},
}),
indicatorsContainer: (provided: any) => ({
...provided,
display: "none",
}),
listBox: (provided: any) => ({
...provided,
color: "red",
fontSize: "16px",
}),
};

View File

@ -1,16 +1,21 @@
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect } from "react";
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;
value?: string; value?: string;
placeholder?: string; placeholder?: string;
disabled?: boolean; disabled?: boolean;
onClear?: () => void;
onFocus?: () => void;
onBlur?: () => void;
}; };
export default function SearchBar({ onChange, value: propValue, placeholder = "Rechercher", disabled = false }: IProps) { export default function SearchBar(props: IProps) {
const { onChange, value: propValue, placeholder = "Rechercher", disabled = false, onClear, onFocus, onBlur } = props;
const [isFocused, setIsFocused] = React.useState(false); const [isFocused, setIsFocused] = React.useState(false);
const [value, setValue] = React.useState(propValue || ""); const [value, setValue] = React.useState(propValue || "");
@ -22,10 +27,25 @@ export default function SearchBar({ onChange, value: propValue, placeholder = "R
[onChange], [onChange],
); );
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value); const handleOnChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value), [changeValue]);
const handleFocus = () => setIsFocused(true);
const handleBlur = () => setIsFocused(false); const handleFocus = useCallback(() => {
const clearValue = () => changeValue(""); setIsFocused(true);
onFocus?.();
}, [onFocus]);
const handleBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement, Element>) => {
setIsFocused(false);
onBlur?.();
},
[onBlur],
);
const clearValue = useCallback(() => {
changeValue("");
onClear?.();
}, [changeValue, onClear]);
useEffect(() => { useEffect(() => {
if (propValue !== undefined) { if (propValue !== undefined) {

View File

@ -1,8 +1,8 @@
import React, { useCallback, useEffect } from "react"; import React, { useEffect } from "react";
import { IBlock } from "../BlockList/Block"; import { IBlock } from "../BlockList/Block";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "../../Typography"; import Dropdown from "../../Dropdown";
import { ChevronDownIcon } from "@heroicons/react/24/outline"; import { IOption } from "../../Dropdown/DropdownMenu/DropdownOption";
type IProps = { type IProps = {
blocks: IBlock[]; blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void; onSelectedBlock: (block: IBlock) => void;
@ -11,72 +11,40 @@ type IProps = {
export default function DropdownNavigation({ blocks, onSelectedBlock, defaultSelectedBlock = blocks[0] }: IProps) { export default function DropdownNavigation({ blocks, onSelectedBlock, defaultSelectedBlock = blocks[0] }: IProps) {
const [selectedBlock, setSelectedBlock] = React.useState<IBlock | null>(defaultSelectedBlock ?? null); const [selectedBlock, setSelectedBlock] = React.useState<IBlock | null>(defaultSelectedBlock ?? null);
const [isDropdownOpened, setIsDropdownOpened] = React.useState<boolean>(false);
const rootRef = React.useRef<HTMLDivElement>(null);
const selectBlock = useCallback(
(id: string) => {
setIsDropdownOpened(false);
setSelectedBlock(blocks.find((folder) => folder.id === id)!);
onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === id)!);
},
[blocks, onSelectedBlock],
);
const handleOnClick = () => setIsDropdownOpened((prev) => !prev);
useEffect(() => {
// on click outside of root, close dropdown
const handleClickOutside = (event: MouseEvent) => {
if (rootRef.current && !rootRef.current.contains(event.target as Node)) {
setIsDropdownOpened(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
useEffect(() => { useEffect(() => {
if (defaultSelectedBlock) setSelectedBlock(defaultSelectedBlock); if (defaultSelectedBlock) setSelectedBlock(defaultSelectedBlock);
}, [defaultSelectedBlock]); }, [defaultSelectedBlock]);
return (
<div className={classes["root"]} data-is-opened={isDropdownOpened} ref={rootRef}>
{selectedBlock && (
<>
<div className={classes["dropdown-header"]} onClick={handleOnClick}>
<div className={classes["text-container"]}>
{selectedBlock.secondaryText && (
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{selectedBlock.secondaryText}
</Typography>
)}
<Typography typo={ETypo.TEXT_MD_BOLD} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{selectedBlock.primaryText}
</Typography>
</div>
<ChevronDownIcon height="24" width="24" color={`var(${ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT})`} />
</div>
{isDropdownOpened && ( const handleDropDownSelect = (option: IOption) => {
<div className={classes["dropdown-content"]}> const block = blocks.find((block) => block.id === option.id);
{blocks if (block) {
.filter((block) => block.id !== selectedBlock.id) setSelectedBlock(block);
.map((block) => ( onSelectedBlock
<div key={block.id} className={classes["dropdown-item"]} onClick={() => selectBlock(block.id)}> ? onSelectedBlock(block)
{block.secondaryText && ( : console.error("DropdownNavigation: onSelectedBlock prop is required to handle block selection");
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}> }
{block.secondaryText} };
</Typography>
)} return (
<Typography typo={ETypo.TEXT_MD_BOLD} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}> <div className={classes["root"]}>
{block.primaryText} <Dropdown
</Typography> options={blocks.map((block) => {
</div> return {
))} id: block.id,
</div> label: {
)} subtext: block.primaryText,
</> text: block.secondaryText,
)} },
} as IOption;
})}
onSelectionChange={handleDropDownSelect}
selectedOption={
selectedBlock
? { id: selectedBlock.id, label: { text: selectedBlock.secondaryText ?? "", subtext: selectedBlock.primaryText } }
: undefined
}
/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
@import "@Themes/constants.scss"; @import "@Themes/constants.scss";
.root { .root {
width: 336px; width: 336px;
min-width: 336px;
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
@ -11,6 +12,7 @@
justify-content: flex-start; justify-content: flex-start;
@media (max-width: $screen-m) { @media (max-width: $screen-m) {
height: auto;
gap: 16px; gap: 16px;
padding: var(--spacing-lg, 24px); padding: var(--spacing-lg, 24px);
width: auto; width: auto;

View File

@ -7,7 +7,7 @@ import Button from "../Button";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import DropdownNavigation from "./DropdownNavigation"; import DropdownNavigation from "./DropdownNavigation";
type IProps = { export type ISearchBlockListProps = {
blocks: IBlock[]; blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void; onSelectedBlock: (block: IBlock) => void;
bottomButton?: { bottomButton?: {
@ -15,7 +15,7 @@ type IProps = {
link: string; link: string;
}; };
}; };
export default function SearchBlockList(props: IProps) { export default function SearchBlockList(props: ISearchBlockListProps) {
const { blocks, onSelectedBlock, bottomButton } = props; const { blocks, onSelectedBlock, bottomButton } = props;
const [selectedBlock, setSelectedBlock] = useState<IBlock | null>(null); const [selectedBlock, setSelectedBlock] = useState<IBlock | null>(null);

View File

@ -159,6 +159,8 @@ export enum ETypoColor {
DROPDOWN_CONTRAST_DEFAULT = "--dropdown-contrast-default", DROPDOWN_CONTRAST_DEFAULT = "--dropdown-contrast-default",
DROPDOWN_CONTRAST_ACTIVE = "--dropdown-contrast-active", DROPDOWN_CONTRAST_ACTIVE = "--dropdown-contrast-active",
INPUT_CHIP_CONTRAST = "--input-chip-contrast",
} }
export default function Typography(props: IProps) { export default function Typography(props: IProps) {

View File

@ -1,23 +0,0 @@
@import "@Themes/constants.scss";
.root {
width: calc(100vh - 83px);
display: flex;
flex-direction: column;
justify-content: space-between;
.header {
flex: 1;
}
.searchbar {
padding: 40px 24px 24px 24px;
}
.folderlist-container {
max-height: calc(100vh - 215px);
height: 100%;
border-right: 1px solid var(--color-neutral-200);
overflow: auto;
}
}

View File

@ -1,44 +0,0 @@
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import User from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = {
collaborators: User[];
onSelectedCollaborator?: (user: User) => void;
onCloseLeftSide?: () => void;
};
export default function CollaboratorListContainer(props: IProps) {
const router = useRouter();
const { collaboratorUid } = router.query;
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.Collaborators.pages.CollaboratorInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],
);
return (
<div className={classes["root"]}>
<SearchBlockList
blocks={props.collaborators.map((user) => {
return {
primaryText: user.contact?.first_name + " " + user.contact?.last_name,
id: user.uid!,
isActive: user.uid === collaboratorUid,
};
})}
onSelectedBlock={onSelectedBlock}
/>
</div>
);
}

View File

@ -1,116 +0,0 @@
@import "@Themes/constants.scss";
@keyframes growWidth {
0% {
width: 100%;
}
100% {
width: 200%;
}
}
.root {
.content {
display: flex;
overflow: hidden;
height: calc(100vh - var(--header-height));
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-generic-white);
opacity: 0.5;
z-index: 2;
transition: all 0.3s $custom-easing;
}
.left-side {
background-color: var(--color-generic-white);
z-index: 3;
display: flex;
width: 336px;
min-width: 336px;
transition: all 0.3s $custom-easing;
overflow: hidden;
@media (max-width: ($screen-m - 1px)) {
width: 56px;
min-width: 56px;
transform: translateX(-389px);
&.opened {
transform: translateX(0px);
width: 336px;
min-width: 336px;
}
}
@media (max-width: $screen-s) {
width: 0px;
min-width: 0px;
&.opened {
width: 100vw;
min-width: 100vw;
}
}
}
.closable-left-side {
position: absolute;
background-color: var(--color-generic-white);
z-index: 0;
display: flex;
justify-content: center;
min-width: 56px;
max-width: 56px;
height: calc(100vh - var(--header-height));
border-right: 1px var(--color-neutral-200) solid;
@media (min-width: $screen-m) {
display: none;
}
.chevron-icon {
margin-top: 21px;
transform: rotate(180deg);
cursor: pointer;
}
@media (max-width: $screen-s) {
display: none;
}
}
.right-side {
min-width: calc(100vw - 389px);
padding: 24px;
overflow-y: auto;
@media (max-width: ($screen-m - 1px)) {
min-width: calc(100vw - 56px);
}
@media (max-width: $screen-s) {
flex: 1;
min-width: unset;
}
.back-arrow-mobile {
display: none;
@media (max-width: $screen-s) {
display: block;
margin-bottom: 24px;
}
}
.back-arrow-desktop {
@media (max-width: $screen-s) {
display: none;
}
}
}
}
}

View File

@ -1,93 +1,20 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import React, { useEffect } from "react";
import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/Admin/Users/Users";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import { useRouter } from "next/router";
import Header from "@Front/Components/DesignSystem/Header"; import Module from "@Front/Config/Module";
import Version from "@Front/Components/DesignSystem/Version"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import BackArrow from "@Front/Components/Elements/BackArrow"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import User from "le-coffre-resources/dist/Notary";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import WindowStore from "@Front/Stores/WindowStore"; import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/Admin/Users/Users";
import classNames from "classnames";
import User from "le-coffre-resources/dist/Admin";
import Image from "next/image";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss"; type IProps = IPropsDashboardWithList;
import CollaboratorListContainer from "./CollaboratorListContainer";
import { ChevronLeftIcon } from "@heroicons/react/24/solid";
type IProps = { export default function DefaultCollaboratorDashboard(props: IProps) {
title: string; const [collaborators, setCollaborators] = React.useState<User[] | null>(null);
children?: ReactNode; const router = useRouter();
onSelectedUser: (user: User) => void; const { collaboratorUid } = router.query;
hasBackArrow: boolean; useEffect(() => {
backArrowUrl?: string;
mobileBackText?: string;
};
type IState = {
collaborators: User[] | null;
isLeftSideOpen: boolean;
leftSideCanBeClosed: boolean;
};
export default class DefaultCollaboratorDashboard extends React.Component<IProps, IState> {
private onWindowResize = () => {};
public static defaultProps: Partial<IProps> = {
hasBackArrow: false,
};
public constructor(props: IProps) {
super(props);
this.state = {
collaborators: null,
isLeftSideOpen: false,
leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false,
};
this.onOpenLeftSide = this.onOpenLeftSide.bind(this);
this.onCloseLeftSide = this.onCloseLeftSide.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />}
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}>
{this.state.collaborators && (
<CollaboratorListContainer collaborators={this.state.collaborators} onCloseLeftSide={this.onCloseLeftSide} />
)}
</div>
<div className={classNames(classes["closable-left-side"])}>
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} />
</div>
<div className={classes["right-side"]}>
{this.props.hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
leftIcon={<ChevronLeftIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
</div>
<Version />
</div>
);
}
public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return; if (!jwt) return;
const query: IGetUsersparams = { const query: IGetUsersparams = {
@ -102,27 +29,31 @@ export default class DefaultCollaboratorDashboard extends React.Component<IProps
}, },
}; };
const collaborators = await Users.getInstance().get(query); Users.getInstance()
this.setState({ collaborators }); .get(query)
} .then((users) => setCollaborators(users));
public override componentWillUnmount() { }, []);
this.onWindowResize();
}
private onOpenLeftSide() { const onSelectedBlock = (block: IBlock) => {
this.setState({ isLeftSideOpen: true }); router.push(
} Module.getInstance().get().modules.pages.Collaborators.pages.CollaboratorInformations.props.path.replace("[uid]", block.id),
);
};
private onCloseLeftSide() { return (
if (!this.state.leftSideCanBeClosed) return; <DefaultDashboardWithList
this.setState({ isLeftSideOpen: false }); {...props}
} onSelectedBlock={onSelectedBlock}
blocks={
private onResize(window: Window) { collaborators
if (window.innerWidth > 1023) { ? collaborators.map((collaborator) => ({
if (!this.state.leftSideCanBeClosed) return; id: collaborator.uid!,
this.setState({ leftSideCanBeClosed: false }); primaryText: collaborator.contact?.first_name + " " + collaborator.contact?.last_name,
} isActive: collaborator.uid === collaboratorUid,
this.setState({ leftSideCanBeClosed: true }); secondaryText: collaborator.contact?.email,
}))
: []
} }
/>
);
} }

View File

@ -0,0 +1,38 @@
@import "@Themes/constants.scss";
.root {
position: relative;
.content {
display: flex;
justify-content: flex-start;
min-height: calc(100vh - var(--header-height));
height: calc(100vh - var(--header-height));
.right-side {
min-width: calc(100% - 336px);
flex: 1;
display: flex;
justify-content: space-between;
flex-direction: column;
.right-side-content {
overflow-y: auto;
padding: var(--spacing-lg, 24px);
height: calc(100% - var(--footer-height));
}
&[data-no-padding="true"] {
.right-side-content {
padding: 0;
}
}
}
@media (max-width: $screen-m) {
width: 100%;
flex-direction: column;
.right-side {
min-width: 100%;
}
}
}
}

View File

@ -0,0 +1,54 @@
import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss";
import SearchBlockList, { ISearchBlockListProps } from "@Front/Components/DesignSystem/SearchBlockList";
import Footer from "@Front/Components/DesignSystem/Footer";
export type IPropsDashboardWithList = {
title?: string;
children?: ReactNode;
hasBackArrow?: boolean;
backArrowUrl?: string;
mobileBackText?: string;
headerConnected?: boolean;
noPadding?: boolean;
};
type IProps = IPropsDashboardWithList & ISearchBlockListProps;
export default function DefaultDashboardWithList(props: IProps) {
const {
hasBackArrow,
backArrowUrl,
children,
blocks,
onSelectedBlock,
headerConnected = true,
bottomButton,
noPadding = false,
} = props;
return (
<div className={classes["root"]}>
<Header isUserConnected={headerConnected} />
<div className={classes["content"]}>
<SearchBlockList blocks={blocks} onSelectedBlock={onSelectedBlock} bottomButton={bottomButton} />
<div className={classes["right-side"]} data-no-padding={noPadding}>
<div className={classes["right-side-content"]}>
{hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={backArrowUrl ?? ""} />
</div>
)}
{children}
</div>
<Footer />
</div>
</div>
<Version />
</div>
);
}

View File

@ -1,32 +0,0 @@
@import "@Themes/constants.scss";
.root {
width: calc(100vh - 83px);
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
.header {
flex: 1;
}
.searchbar {
padding: 40px 24px 24px 24px;
}
.folderlist-container {
max-height: calc(100vh - 290px);
height: calc(100vh - 290px);
overflow: auto;
border-right: 1px solid var(--color-neutral-200);
}
.create-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
}

View File

@ -1,48 +0,0 @@
import DeedTypes from "@Front/Api/LeCoffreApi/Admin/DeedTypes/DeedTypes";
import Module from "@Front/Config/Module";
import { DeedType } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = {
deedTypes: DeedType[];
onSelectedDeed?: (deed: DeedTypes) => void;
onCloseLeftSide?: () => void;
};
export default function DeedListContainer(props: IProps) {
const router = useRouter();
const { deedTypeUid } = router.query;
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],
);
return (
<div className={classes["root"]}>
<SearchBlockList
blocks={props.deedTypes.map((deed) => {
return {
primaryText: deed.name,
id: deed.uid!,
isActive: deedTypeUid === deed.uid,
};
})}
onSelectedBlock={onSelectedBlock}
bottomButton={{
link: Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
text: "Créer un type d'acte",
}}
/>
</div>
);
}

View File

@ -1,116 +0,0 @@
@import "@Themes/constants.scss";
@keyframes growWidth {
0% {
width: 100%;
}
100% {
width: 200%;
}
}
.root {
.content {
display: flex;
overflow: hidden;
height: calc(100vh - var(--header-height));
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-generic-white);
opacity: 0.5;
z-index: 2;
transition: all 0.3s $custom-easing;
}
.left-side {
background-color: var(--color-generic-white);
z-index: 3;
display: flex;
width: 336px;
min-width: 336px;
transition: all 0.3s $custom-easing;
overflow: hidden;
@media (max-width: ($screen-m - 1px)) {
width: 56px;
min-width: 56px;
transform: translateX(-389px);
&.opened {
transform: translateX(0px);
width: 336px;
min-width: 336px;
}
}
@media (max-width: $screen-s) {
width: 0px;
min-width: 0px;
&.opened {
width: 100vw;
min-width: 100vw;
}
}
}
.closable-left-side {
position: absolute;
background-color: var(--color-generic-white);
z-index: 0;
display: flex;
justify-content: center;
min-width: 56px;
max-width: 56px;
height: calc(100vh - var(--header-height));
border-right: 1px var(--color-neutral-200) solid;
@media (min-width: $screen-m) {
display: none;
}
.chevron-icon {
margin-top: 21px;
transform: rotate(180deg);
cursor: pointer;
}
@media (max-width: $screen-s) {
display: none;
}
}
.right-side {
min-width: calc(100vw - 389px);
padding: 24px;
overflow-y: auto;
@media (max-width: ($screen-m - 1px)) {
min-width: calc(100vw - 56px);
}
@media (max-width: $screen-s) {
flex: 1;
min-width: unset;
}
.back-arrow-mobile {
display: none;
@media (max-width: $screen-s) {
display: block;
margin-bottom: 24px;
}
}
.back-arrow-desktop {
@media (max-width: $screen-s) {
display: none;
}
}
}
}
}

View File

@ -1,92 +1,19 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import React, { useEffect } from "react";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import { DeedType } from "le-coffre-resources/dist/Notary";
import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes"; import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow";
import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames";
import { Deed, DeedType } from "le-coffre-resources/dist/Notary";
import Image from "next/image";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss"; type IProps = IPropsDashboardWithList;
import DeedListContainer from "./DeedTypeListContainer";
import { ChevronLeftIcon } from "@heroicons/react/24/solid";
type IProps = { export default function DefaultDeedTypeDashboard(props: IProps) {
title: string; const [deedTypes, setDeedTypes] = React.useState<DeedType[] | null>(null);
children?: ReactNode; const router = useRouter();
onSelectedDeed: (deed: Deed) => void; const { deedTypeUid } = router.query;
hasBackArrow: boolean; useEffect(() => {
backArrowUrl?: string;
mobileBackText?: string;
};
type IState = {
deedTypes: DeedType[] | null;
isLeftSideOpen: boolean;
leftSideCanBeClosed: boolean;
};
export default class DefaultDeedTypesDashboard extends React.Component<IProps, IState> {
private onWindowResize = () => {};
public static defaultProps: Partial<IProps> = {
hasBackArrow: false,
};
public constructor(props: IProps) {
super(props);
this.state = {
deedTypes: null,
isLeftSideOpen: false,
leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false,
};
this.onOpenLeftSide = this.onOpenLeftSide.bind(this);
this.onCloseLeftSide = this.onCloseLeftSide.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />}
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}>
{this.state.deedTypes && (
<DeedListContainer deedTypes={this.state.deedTypes} onCloseLeftSide={this.onCloseLeftSide} />
)}
</div>
<div className={classNames(classes["closable-left-side"])}>
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} />
</div>
<div className={classes["right-side"]}>
{this.props.hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
leftIcon={<ChevronLeftIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
</div>
<Version />
</div>
);
}
public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
const query: IGetDeedTypesParams = { const query: IGetDeedTypesParams = {
where: { where: {
archived_at: null, archived_at: null,
@ -96,28 +23,32 @@ export default class DefaultDeedTypesDashboard extends React.Component<IProps, I
}, },
}; };
const deedTypes = await DeedTypes.getInstance().get(query); DeedTypes.getInstance()
this.setState({ deedTypes }); .get(query)
} .then((deedTypes) => setDeedTypes(deedTypes));
}, []);
public override componentWillUnmount() { const onSelectedBlock = (block: IBlock) => {
this.onWindowResize(); router.push(Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", block.id));
} };
private onOpenLeftSide() { return (
this.setState({ isLeftSideOpen: true }); <DefaultDashboardWithList
} {...props}
onSelectedBlock={onSelectedBlock}
private onCloseLeftSide() { blocks={
if (!this.state.leftSideCanBeClosed) return; deedTypes
this.setState({ isLeftSideOpen: false }); ? deedTypes.map((deedTypes) => ({
} id: deedTypes.uid!,
primaryText: deedTypes.name,
private onResize(window: Window) { isActive: deedTypes.uid === deedTypeUid,
if (window.innerWidth > 1023) { }))
if (!this.state.leftSideCanBeClosed) return; : []
this.setState({ leftSideCanBeClosed: false });
}
this.setState({ leftSideCanBeClosed: true });
} }
bottomButton={{
link: Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
text: "Créer une liste de pièces",
}}
/>
);
} }

View File

@ -1,30 +0,0 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
.header {
flex: 1;
}
.searchbar {
padding: 40px 24px 24px 24px;
}
.folderlist-container {
max-height: calc(100vh - 290px);
height: calc(100vh - 290px);
overflow: auto;
border-right: 1px solid var(--color-neutral-200);
}
.create-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
}

View File

@ -1,48 +0,0 @@
import Module from "@Front/Config/Module";
import { DocumentType } from "le-coffre-resources/dist/SuperAdmin";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = {
documentTypes: DocumentType[];
onSelectedDocumentType?: (documentType: DocumentType) => void;
onCloseLeftSide?: () => void;
};
export default function DocumentTypeListContainer(props: IProps) {
const router = useRouter();
const { documentTypeUid } = router.query;
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],
);
return (
<div className={classes["root"]}>
<SearchBlockList
blocks={props.documentTypes.map((documentType) => {
return {
primaryText: documentType.name,
id: documentType.uid!,
isActive: documentType.uid === documentTypeUid,
};
})}
onSelectedBlock={onSelectedBlock}
bottomButton={{
link: Module.getInstance().get().modules.pages.DocumentTypes.pages.Create.props.path,
text: "Créer un type de document",
}}
/>
</div>
);
}

View File

@ -1,116 +0,0 @@
@import "@Themes/constants.scss";
@keyframes growWidth {
0% {
width: 100%;
}
100% {
width: 200%;
}
}
.root {
.content {
display: flex;
overflow: hidden;
height: calc(100vh - var(--header-height));
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-generic-white);
opacity: 0.5;
z-index: 2;
transition: all 0.3s $custom-easing;
}
.left-side {
background-color: var(--color-generic-white);
z-index: 3;
display: flex;
width: 336px;
min-width: 336px;
transition: all 0.3s $custom-easing;
overflow: hidden;
@media (max-width: ($screen-m - 1px)) {
width: 56px;
min-width: 56px;
transform: translateX(-389px);
&.opened {
transform: translateX(0px);
width: 336px;
min-width: 336px;
}
}
@media (max-width: $screen-s) {
width: 0px;
min-width: 0px;
&.opened {
width: 100vw;
min-width: 100vw;
}
}
}
.closable-left-side {
position: absolute;
background-color: var(--color-generic-white);
z-index: 0;
display: flex;
justify-content: center;
min-width: 56px;
max-width: 56px;
height: calc(100vh - var(--header-height));
border-right: 1px var(--color-neutral-200) solid;
@media (min-width: $screen-m) {
display: none;
}
.chevron-icon {
margin-top: 21px;
transform: rotate(180deg);
cursor: pointer;
}
@media (max-width: $screen-s) {
display: none;
}
}
.right-side {
min-width: calc(100vw - 389px);
padding: 24px;
overflow-y: auto;
@media (max-width: ($screen-m - 1px)) {
min-width: calc(100vw - 56px);
}
@media (max-width: $screen-s) {
flex: 1;
min-width: unset;
}
.back-arrow-mobile {
display: none;
@media (max-width: $screen-s) {
display: block;
margin-bottom: 24px;
}
}
.back-arrow-desktop {
@media (max-width: $screen-s) {
display: none;
}
}
}
}
}

View File

@ -1,124 +1,57 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import React, { useEffect } from "react";
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import { useRouter } from "next/router";
import Header from "@Front/Components/DesignSystem/Header"; import Module from "@Front/Config/Module";
import Version from "@Front/Components/DesignSystem/Version"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import BackArrow from "@Front/Components/Elements/BackArrow"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import WindowStore from "@Front/Stores/WindowStore"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import classNames from "classnames";
import { DocumentType } from "le-coffre-resources/dist/Notary"; import { DocumentType } from "le-coffre-resources/dist/Notary";
import Image from "next/image";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss"; type IProps = IPropsDashboardWithList;
import DocumentTypeListContainer from "./DocumentTypeListContainer";
import { ChevronLeftIcon } from "@heroicons/react/24/solid";
type IProps = { export default function DefaultDocumentTypeDashboard(props: IProps) {
title: string; const [documentTypes, setDocumentTypes] = React.useState<DocumentType[] | null>(null);
children?: ReactNode; const router = useRouter();
onSelectedDocumentType: (documentType: DocumentType) => void; const { documentTypeUid } = router.query;
hasBackArrow: boolean; useEffect(() => {
backArrowUrl?: string;
mobileBackText?: string;
};
type IState = {
documentTypes: DocumentType[] | null;
isLeftSideOpen: boolean;
leftSideCanBeClosed: boolean;
};
export default class DefaultDocumentTypesDashboard extends React.Component<IProps, IState> {
private onWindowResize = () => {};
public static defaultProps: Partial<IProps> = {
hasBackArrow: false,
};
public constructor(props: IProps) {
super(props);
this.state = {
documentTypes: null,
isLeftSideOpen: false,
leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false,
};
this.onOpenLeftSide = this.onOpenLeftSide.bind(this);
this.onCloseLeftSide = this.onCloseLeftSide.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />}
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}>
{this.state.documentTypes && (
<DocumentTypeListContainer documentTypes={this.state.documentTypes} onCloseLeftSide={this.onCloseLeftSide} />
)}
</div>
<div className={classNames(classes["closable-left-side"])}>
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} />
</div>
<div className={classes["right-side"]}>
{this.props.hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
leftIcon={<ChevronLeftIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
</div>
<Version />
</div>
);
}
public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return; if (!jwt) return;
const documentTypes = await DocumentTypes.getInstance().get({ DocumentTypes.getInstance()
.get({
where: { where: {
office_uid: jwt.office_Id, office_uid: jwt.office_Id,
}, },
orderBy: { orderBy: {
name: "asc", name: "asc",
}, },
}); })
this.setState({ documentTypes }); .then((documentTypes) => setDocumentTypes(documentTypes));
} }, []);
public override componentWillUnmount() { const onSelectedBlock = (block: IBlock) => {
this.onWindowResize(); router.push(
} Module.getInstance().get().modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", block.id),
);
};
private onOpenLeftSide() { return (
this.setState({ isLeftSideOpen: true }); <DefaultDashboardWithList
} {...props}
onSelectedBlock={onSelectedBlock}
private onCloseLeftSide() { blocks={
if (!this.state.leftSideCanBeClosed) return; documentTypes
this.setState({ isLeftSideOpen: false }); ? documentTypes.map((documentType) => ({
} id: documentType.uid!,
primaryText: documentType.name,
private onResize(window: Window) { isActive: documentType.uid === documentTypeUid,
if (window.innerWidth > 1023) { }))
if (!this.state.leftSideCanBeClosed) return; : []
this.setState({ leftSideCanBeClosed: false });
}
this.setState({ leftSideCanBeClosed: true });
} }
bottomButton={{
link: Module.getInstance().get().modules.pages.DocumentTypes.pages.Create.props.path,
text: "Créer un type de document",
}}
/>
);
} }

View File

@ -5,7 +5,7 @@
.content { .content {
display: flex; display: flex;
.sides { .sides {
min-height: calc(100vh - var(--header-height)); min-height: calc(100vh - var(--header-height) - var(--footer-height));
width: 50%; width: 50%;
@media (max-width: $screen-m) { @media (max-width: $screen-m) {
width: 100%; width: 100%;

View File

@ -5,6 +5,7 @@ import Image, { StaticImageData } from "next/image";
import React, { ReactNode, useEffect } from "react"; import React, { ReactNode, useEffect } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Footer from "@Front/Components/DesignSystem/Footer";
type IProps = { type IProps = {
title: string; title: string;
@ -45,6 +46,7 @@ export default function DefaultDoubleSidePage(props: IProps) {
<Image alt={"right side image"} src={image} className={classes["background-image"]} priority /> <Image alt={"right side image"} src={image} className={classes["background-image"]} priority />
</div> </div>
)} )}
<Footer hasLeftPadding isSticky />
</div> </div>
); );
} }

View File

@ -0,0 +1,57 @@
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import { useRouter } from "next/router";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import { useMemo } from "react";
import Module from "@Front/Config/Module";
type IProps = IPropsDashboardWithList;
export enum ELegalOptions {
LEGAL_MENTIONS = "mentions-legales",
CGU = "cgu",
CGS = "cgs",
POLITIQUE_DE_CONFIDENTIALITE = "politique-de-confidentialite",
POLITIQUE_DE_GESTION_DES_COOKIES = "politique-de-gestion-des-cookies",
}
export default function DefaultLegalDashboard(props: IProps) {
const router = useRouter();
const { legalUid } = router.query;
const onSelectedBlock = (block: IBlock) => {
router.push(Module.getInstance().get().modules.pages.Legal.pages.LegalInformations.props.path.replace("[legalUid]", block.id));
};
const blocks: IBlock[] = useMemo<IBlock[]>(
() => [
{
id: ELegalOptions.LEGAL_MENTIONS,
primaryText: "Mentions légales",
isActive: legalUid === ELegalOptions.LEGAL_MENTIONS,
},
{
id: ELegalOptions.CGU,
primaryText: "CGU",
isActive: legalUid === ELegalOptions.CGU,
},
{
id: ELegalOptions.CGS,
primaryText: "CGS",
isActive: legalUid === ELegalOptions.CGS,
},
{
id: ELegalOptions.POLITIQUE_DE_CONFIDENTIALITE,
primaryText: "Politique de confidentialité",
isActive: legalUid === ELegalOptions.POLITIQUE_DE_CONFIDENTIALITE,
},
{
id: ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES,
primaryText: "Politique de gestion des cookies",
isActive: legalUid === ELegalOptions.POLITIQUE_DE_GESTION_DES_COOKIES,
},
],
[legalUid],
);
return <DefaultDashboardWithList {...props} onSelectedBlock={onSelectedBlock} blocks={blocks} noPadding />;
}

View File

@ -1,23 +0,0 @@
@import "@Themes/constants.scss";
.root {
position: relative;
.content {
display: flex;
justify-content: flex-start;
height: calc(100vh - var(--header-height));
@media (max-width: $screen-m) {
flex-direction: column;
}
.right-side {
min-width: calc(100% - 336px);
overflow-y: auto;
padding: var(--spacing-lg, 24px);
@media (max-width: $screen-m) {
width: 100%;
}
}
}
}

View File

@ -1,28 +1,77 @@
import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import FolderListContainer from "@Front/Components/DesignSystem/FolderListContainer";
import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus"; import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import React, { ReactNode, useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import classes from "./classes.module.scss"; import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import { useRouter } from "next/router";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
type IProps = { type IProps = IPropsDashboardWithList & {
title: string;
children?: ReactNode;
isArchived?: boolean; isArchived?: boolean;
onSelectedFolder?: (folder: OfficeFolder) => void;
hasBackArrow?: boolean;
backArrowUrl?: string;
mobileBackText?: string;
}; };
export default function DefaultNotaryDashboard(props: IProps) { export default function DefaultNotaryDashboard(props: IProps) {
const { hasBackArrow, onSelectedFolder, backArrowUrl, children, isArchived } = props; const { isArchived = false } = props;
const router = useRouter();
const [folders, setFolders] = React.useState<OfficeFolder[]>([]); const [folders, setFolders] = React.useState<OfficeFolder[]>([]);
const { folderUid } = router.query;
const redirectPath: string = isArchived
? Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.pages.FolderInformation.props.path
: Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path;
const getBlocks = useCallback(
(folders: OfficeFolder[]): IBlock[] => {
const pendingFolders = folders
.filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED,
);
return pendingDocuments.length >= 1;
})
.sort((folder1, folder2) => {
return folder1.created_at! > folder2.created_at! ? -1 : 1;
});
const otherFolders = folders
.filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED,
);
return pendingDocuments.length === 0;
})
.sort((folder1, folder2) => {
return folder1.created_at! > folder2.created_at! ? -1 : 1;
});
return [...pendingFolders, ...otherFolders].map((folder) => {
return {
id: folder.uid!,
primaryText: folder.name,
secondaryText: folder.folder_number,
isActive: folderUid === folder.uid,
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
};
});
},
[folderUid],
);
const [blocks, setBlocks] = React.useState<IBlock[]>(getBlocks(folders));
const onSelectedBlock = (block: IBlock) => {
const folder = folders.find((folder) => folder.uid === block.id);
if (!folder) return;
const path = redirectPath.replace("[folderUid]", folder.uid ?? "");
router.push(path);
};
useEffect(() => {
setBlocks(getBlocks(folders));
}, [folders, getBlocks]);
useEffect(() => { useEffect(() => {
let targetedStatus: EFolderStatus = EFolderStatus["LIVE" as keyof typeof EFolderStatus]; let targetedStatus: EFolderStatus = EFolderStatus["LIVE" as keyof typeof EFolderStatus];
@ -64,20 +113,14 @@ export default function DefaultNotaryDashboard(props: IProps) {
}, [isArchived]); }, [isArchived]);
return ( return (
<div className={classes["root"]}> <DefaultDashboardWithList
<Header isUserConnected={true} /> {...props}
<div className={classes["content"]}> onSelectedBlock={onSelectedBlock}
<FolderListContainer folders={folders} onSelectedFolder={onSelectedFolder} isArchived={isArchived ?? false} /> blocks={blocks}
<div className={classes["right-side"]}> bottomButton={{
{hasBackArrow && ( link: Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path,
<div className={classes["back-arrow-desktop"]}> text: "Créer un dossier",
<BackArrow url={backArrowUrl ?? ""} /> }}
</div> />
)}
{children}
</div>
</div>
<Version />
</div>
); );
} }

View File

@ -1,23 +0,0 @@
@import "@Themes/constants.scss";
.root {
width: calc(100vh - 83px);
display: flex;
flex-direction: column;
justify-content: space-between;
.header {
flex: 1;
}
.searchbar {
padding: 40px 24px 24px 24px;
}
.folderlist-container {
max-height: 100vh;
height: 100vh;
overflow: auto;
border-right: 1px solid var(--color-neutral-200);
}
}

View File

@ -1,44 +0,0 @@
import Module from "@Front/Config/Module";
import { Office } from "le-coffre-resources/dist/SuperAdmin";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = {
offices: Office[];
onSelectedOffice?: (office: Office) => void;
onCloseLeftSide?: () => void;
};
export default function OfficeListContainer(props: IProps) {
const router = useRouter();
const { officeUid } = router.query;
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.Offices.pages.OfficesInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],
);
return (
<div className={classes["root"]}>
<SearchBlockList
blocks={props.offices.map((office) => {
return {
primaryText: office.crpcen + " - " + office.name,
id: office.uid!,
isActive: office.uid === officeUid,
};
})}
onSelectedBlock={onSelectedBlock}
/>
</div>
);
}

View File

@ -1,116 +0,0 @@
@import "@Themes/constants.scss";
@keyframes growWidth {
0% {
width: 100%;
}
100% {
width: 200%;
}
}
.root {
.content {
display: flex;
overflow: hidden;
height: calc(100vh - var(--header-height));
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-generic-white);
opacity: 0.5;
z-index: 2;
transition: all 0.3s $custom-easing;
}
.left-side {
background-color: var(--color-generic-white);
z-index: 3;
display: flex;
width: 336px;
min-width: 336px;
transition: all 0.3s $custom-easing;
overflow: hidden;
@media (max-width: ($screen-m - 1px)) {
width: 56px;
min-width: 56px;
transform: translateX(-389px);
&.opened {
transform: translateX(0px);
width: 336px;
min-width: 336px;
}
}
@media (max-width: $screen-s) {
width: 0px;
min-width: 0px;
&.opened {
width: 100vw;
min-width: 100vw;
}
}
}
.closable-left-side {
position: absolute;
background-color: var(--color-generic-white);
z-index: 0;
display: flex;
justify-content: center;
min-width: 56px;
max-width: 56px;
height: calc(100vh - var(--header-height));
border-right: 1px var(--color-neutral-200) solid;
@media (min-width: $screen-m) {
display: none;
}
.chevron-icon {
margin-top: 21px;
transform: rotate(180deg);
cursor: pointer;
}
@media (max-width: $screen-s) {
display: none;
}
}
.right-side {
min-width: calc(100vw - 389px);
padding: 24px;
overflow-y: auto;
@media (max-width: ($screen-m - 1px)) {
min-width: calc(100vw - 56px);
}
@media (max-width: $screen-s) {
flex: 1;
min-width: unset;
}
.back-arrow-mobile {
display: none;
@media (max-width: $screen-s) {
display: block;
margin-bottom: 24px;
}
}
.back-arrow-desktop {
@media (max-width: $screen-s) {
display: none;
}
}
}
}
}

View File

@ -1,112 +1,42 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import { Office } from "le-coffre-resources/dist/SuperAdmin";
import React, { useEffect } from "react";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import Offices from "@Front/Api/LeCoffreApi/SuperAdmin/Offices/Offices"; import Offices from "@Front/Api/LeCoffreApi/SuperAdmin/Offices/Offices";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow";
import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames";
import { Office } from "le-coffre-resources/dist/Notary";
import Image from "next/image";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss"; type IProps = IPropsDashboardWithList;
import OfficeListContainer from "./OfficeListContainer";
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
type IProps = { export default function DefaultOfficeDashboard(props: IProps) {
title: string; const [offices, setOffices] = React.useState<Office[] | null>(null);
children?: ReactNode; const router = useRouter();
onSelectedOffice: (office: Office) => void; const { officeUid } = router.query;
hasBackArrow: boolean; useEffect(() => {
backArrowUrl?: string; Offices.getInstance()
mobileBackText?: string; .get()
}; .then((offices) => setOffices(offices));
type IState = { }, []);
offices: Office[] | null;
isLeftSideOpen: boolean;
leftSideCanBeClosed: boolean;
};
export default class DefaultOfficeDashboard extends React.Component<IProps, IState> { const onSelectedBlock = (block: IBlock) => {
private onWindowResize = () => {}; router.push(Module.getInstance().get().modules.pages.Offices.pages.OfficesInformations.props.path.replace("[uid]", block.id));
public static defaultProps: Partial<IProps> = {
hasBackArrow: false,
}; };
public constructor(props: IProps) {
super(props);
this.state = {
offices: null,
isLeftSideOpen: false,
leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false,
};
this.onOpenLeftSide = this.onOpenLeftSide.bind(this);
this.onCloseLeftSide = this.onCloseLeftSide.bind(this);
}
public override render(): JSX.Element {
return ( return (
<div className={classes["root"]}> <DefaultDashboardWithList
<Header isUserConnected={true} /> {...props}
<div className={classes["content"]}> onSelectedBlock={onSelectedBlock}
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />} blocks={
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}> offices
{this.state.offices && <OfficeListContainer offices={this.state.offices} onCloseLeftSide={this.onCloseLeftSide} />} ? offices.map((office) => ({
</div> id: office.uid!,
<div className={classNames(classes["closable-left-side"])}> primaryText: office.name,
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} /> isActive: office.uid === officeUid,
</div> secondaryText: office.crpcen,
}))
<div className={classes["right-side"]}> : []
{this.props.hasBackArrow && ( }
<div className={classes["back-arrow-desktop"]}> />
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
leftIcon={<ChevronLeftIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
</div>
<Version />
</div>
); );
}
public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
const offices = await Offices.getInstance().get();
this.setState({ offices });
}
public override componentWillUnmount() {
this.onWindowResize();
}
private onOpenLeftSide() {
this.setState({ isLeftSideOpen: true });
}
private onCloseLeftSide() {
if (!this.state.leftSideCanBeClosed) return;
this.setState({ isLeftSideOpen: false });
}
private onResize(window: Window) {
if (window.innerWidth > 1023) {
if (!this.state.leftSideCanBeClosed) return;
this.setState({ leftSideCanBeClosed: false });
}
this.setState({ leftSideCanBeClosed: true });
}
} }

View File

@ -1,32 +0,0 @@
@import "@Themes/constants.scss";
.root {
width: calc(100vh - 83px);
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
.header {
flex: 1;
}
.searchbar {
padding: 40px 24px 24px 24px;
}
.folderlist-container {
max-height: calc(100vh - 215px);
height: calc(100vh - 215px);
overflow: auto;
border-right: 1px solid var(--color-neutral-200);
}
.create-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
}

View File

@ -1,53 +0,0 @@
import Module from "@Front/Config/Module";
import { OfficeRole } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = {
roles: OfficeRole[];
onSelectedRole?: (role: OfficeRole) => void;
onCloseLeftSide?: () => void;
};
export default function RoleListContainer(props: IProps) {
const router = useRouter();
const { roleUid } = router.query;
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],
);
return (
<div className={classes["root"]}>
<SearchBlockList
blocks={props.roles
.filter((role) => {
if (role.name === "admin") return false;
return true;
})
.map((role) => {
return {
primaryText: role.name,
id: role.uid!,
isActive: role.uid === roleUid,
};
})}
onSelectedBlock={onSelectedBlock}
bottomButton={{
link: Module.getInstance().get().modules.pages.Roles.pages.Create.props.path,
text: "Créer un rôle",
}}
/>
</div>
);
}

View File

@ -1,116 +0,0 @@
@import "@Themes/constants.scss";
@keyframes growWidth {
0% {
width: 100%;
}
100% {
width: 200%;
}
}
.root {
.content {
display: flex;
overflow: hidden;
height: calc(100vh - var(--header-height));
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-generic-white);
opacity: 0.5;
z-index: 2;
transition: all 0.3s $custom-easing;
}
.left-side {
background-color: var(--color-generic-white);
z-index: 3;
display: flex;
width: 336px;
min-width: 336px;
transition: all 0.3s $custom-easing;
overflow: hidden;
@media (max-width: ($screen-m - 1px)) {
width: 56px;
min-width: 56px;
transform: translateX(-389px);
&.opened {
transform: translateX(0px);
width: 336px;
min-width: 336px;
}
}
@media (max-width: $screen-s) {
width: 0px;
min-width: 0px;
&.opened {
width: 100vw;
min-width: 100vw;
}
}
}
.closable-left-side {
position: absolute;
background-color: var(--color-generic-white);
z-index: 0;
display: flex;
justify-content: center;
min-width: 56px;
max-width: 56px;
height: calc(100vh - var(--header-height));
border-right: 1px var(--color-neutral-200) solid;
@media (min-width: $screen-m) {
display: none;
}
.chevron-icon {
margin-top: 21px;
transform: rotate(180deg);
cursor: pointer;
}
@media (max-width: $screen-s) {
display: none;
}
}
.right-side {
min-width: calc(100vw - 389px);
padding: 24px;
overflow-y: auto;
@media (max-width: ($screen-m - 1px)) {
min-width: calc(100vw - 56px);
}
@media (max-width: $screen-s) {
flex: 1;
min-width: unset;
}
.back-arrow-mobile {
display: none;
@media (max-width: $screen-s) {
display: block;
margin-bottom: 24px;
}
}
.back-arrow-desktop {
@media (max-width: $screen-s) {
display: none;
}
}
}
}
}

View File

@ -1,117 +1,49 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import React, { useEffect } from "react";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import { OfficeRole } from "le-coffre-resources/dist/Notary";
import OfficeRoles, { IGetRolesParams } from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles"; import OfficeRoles, { IGetRolesParams } from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow";
import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames";
import { OfficeRole } from "le-coffre-resources/dist/Admin";
import Image from "next/image";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss"; type IProps = IPropsDashboardWithList;
import RoleListContainer from "./RoleListContainer";
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
type IProps = { export default function DefaultRoleDashboard(props: IProps) {
title: string; const [roles, setRoles] = React.useState<OfficeRole[] | null>(null);
children?: ReactNode; const router = useRouter();
onSelectedRole: (role: OfficeRole) => void; const { roleUid } = router.query;
hasBackArrow: boolean; useEffect(() => {
backArrowUrl?: string;
mobileBackText?: string;
};
type IState = {
roles: OfficeRole[] | null;
isLeftSideOpen: boolean;
leftSideCanBeClosed: boolean;
};
export default class DefaultRoleDashboard extends React.Component<IProps, IState> {
private onWindowResize = () => {};
public static defaultProps: Partial<IProps> = {
hasBackArrow: false,
};
public constructor(props: IProps) {
super(props);
this.state = {
roles: null,
isLeftSideOpen: false,
leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false,
};
this.onOpenLeftSide = this.onOpenLeftSide.bind(this);
this.onCloseLeftSide = this.onCloseLeftSide.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />}
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}>
{this.state.roles && <RoleListContainer roles={this.state.roles} onCloseLeftSide={this.onCloseLeftSide} />}
</div>
<div className={classNames(classes["closable-left-side"])}>
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} />
</div>
<div className={classes["right-side"]}>
{this.props.hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
leftIcon={<ChevronLeftIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
</div>
<Version />
</div>
);
}
public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
const query: IGetRolesParams = { const query: IGetRolesParams = {
include: { rules: true }, include: { rules: true },
}; };
const roles = await OfficeRoles.getInstance().get(query); OfficeRoles.getInstance()
.get(query)
.then((roles) => setRoles(roles));
}, []);
this.setState({ roles }); const onSelectedBlock = (block: IBlock) => {
} router.push(Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path.replace("[uid]", block.id));
};
public override componentWillUnmount() { return (
this.onWindowResize(); <DefaultDashboardWithList
} {...props}
onSelectedBlock={onSelectedBlock}
private onOpenLeftSide() { blocks={
this.setState({ isLeftSideOpen: true }); roles
} ? roles.map((role) => ({
id: role.uid!,
private onCloseLeftSide() { primaryText: role.name,
if (!this.state.leftSideCanBeClosed) return; isActive: role.uid === roleUid,
this.setState({ isLeftSideOpen: false }); }))
} : []
private onResize(window: Window) {
if (window.innerWidth > 1023) {
if (!this.state.leftSideCanBeClosed) return;
this.setState({ leftSideCanBeClosed: false });
}
this.setState({ leftSideCanBeClosed: true });
} }
bottomButton={{
link: Module.getInstance().get().modules.pages.Roles.pages.Create.props.path,
text: "Créer un rôle",
}}
/>
);
} }

View File

@ -1,23 +0,0 @@
@import "@Themes/constants.scss";
.root {
width: calc(100vh - 83px);
display: flex;
flex-direction: column;
justify-content: space-between;
.header {
flex: 1;
}
.searchbar {
padding: 40px 24px 24px 24px;
}
.folderlist-container {
max-height: 100vh;
height: 100vh;
overflow: auto;
border-right: 1px solid var(--color-neutral-200);
}
}

View File

@ -1,44 +0,0 @@
import Module from "@Front/Config/Module";
import User from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
type IProps = {
users: User[];
onSelectedUser?: (user: User) => void;
onCloseLeftSide?: () => void;
};
export default function UserListContainer(props: IProps) {
const router = useRouter();
const { userUid } = router.query;
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
const redirectPath = Module.getInstance().get().modules.pages.Users.pages.UsersInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},
[props, router],
);
return (
<div className={classes["root"]}>
<SearchBlockList
blocks={props.users.map((user) => {
return {
primaryText: user.contact?.first_name + " " + user.contact?.last_name,
id: user.uid!,
isActive: user.uid === userUid,
};
})}
onSelectedBlock={onSelectedBlock}
/>
</div>
);
}

View File

@ -1,116 +0,0 @@
@import "@Themes/constants.scss";
@keyframes growWidth {
0% {
width: 100%;
}
100% {
width: 200%;
}
}
.root {
.content {
display: flex;
overflow: hidden;
height: calc(100vh - var(--header-height));
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-generic-white);
opacity: 0.5;
z-index: 2;
transition: all 0.3s $custom-easing;
}
.left-side {
background-color: var(--color-generic-white);
z-index: 3;
display: flex;
width: 336px;
min-width: 336px;
transition: all 0.3s $custom-easing;
overflow: hidden;
@media (max-width: ($screen-m - 1px)) {
width: 56px;
min-width: 56px;
transform: translateX(-389px);
&.opened {
transform: translateX(0px);
width: 336px;
min-width: 336px;
}
}
@media (max-width: $screen-s) {
width: 0px;
min-width: 0px;
&.opened {
width: 100vw;
min-width: 100vw;
}
}
}
.closable-left-side {
position: absolute;
background-color: var(--color-generic-white);
z-index: 0;
display: flex;
justify-content: center;
min-width: 56px;
max-width: 56px;
height: calc(100vh - var(--header-height));
border-right: 1px var(--color-neutral-200 solid);
@media (min-width: $screen-m) {
display: none;
}
.chevron-icon {
margin-top: 21px;
transform: rotate(180deg);
cursor: pointer;
}
@media (max-width: $screen-s) {
display: none;
}
}
.right-side {
min-width: calc(100vw - 389px);
padding: 24px;
overflow-y: auto;
@media (max-width: ($screen-m - 1px)) {
min-width: calc(100vw - 56px);
}
@media (max-width: $screen-s) {
flex: 1;
min-width: unset;
}
.back-arrow-mobile {
display: none;
@media (max-width: $screen-s) {
display: block;
margin-bottom: 24px;
}
}
.back-arrow-desktop {
@media (max-width: $screen-s) {
display: none;
}
}
}
}
}

View File

@ -1,115 +1,46 @@
import ChevronIcon from "@Assets/Icons/chevron.svg";
import { ChevronLeftIcon } from "@heroicons/react/20/solid";
import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow";
import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames";
import User from "le-coffre-resources/dist/SuperAdmin"; import User from "le-coffre-resources/dist/SuperAdmin";
import Image from "next/image"; import React, { useEffect } from "react";
import React, { ReactNode } from "react";
import classes from "./classes.module.scss"; import { useRouter } from "next/router";
import UserListContainer from "./UserListContainer"; import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
type IProps = { type IProps = IPropsDashboardWithList;
title: string;
children?: ReactNode;
onSelectedUser: (user: User) => void;
hasBackArrow: boolean;
backArrowUrl?: string;
mobileBackText?: string;
};
type IState = {
users: User[] | null;
isLeftSideOpen: boolean;
leftSideCanBeClosed: boolean;
};
export default class DefaultUserDashboard extends React.Component<IProps, IState> { export default function DefaultUserDashboard(props: IProps) {
private onWindowResize = () => {}; const [users, setUsers] = React.useState<User[] | null>(null);
public static defaultProps: Partial<IProps> = { const router = useRouter();
hasBackArrow: false, const { userUid } = router.query;
}; useEffect(() => {
public constructor(props: IProps) {
super(props);
this.state = {
users: null,
isLeftSideOpen: false,
leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false,
};
this.onOpenLeftSide = this.onOpenLeftSide.bind(this);
this.onCloseLeftSide = this.onCloseLeftSide.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />}
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}>
{this.state.users && <UserListContainer users={this.state.users} onCloseLeftSide={this.onCloseLeftSide} />}
</div>
<div className={classNames(classes["closable-left-side"])}>
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} />
</div>
<div className={classes["right-side"]}>
{this.props.hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
leftIcon={<ChevronLeftIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
</div>
<Version />
</div>
);
}
public override async componentDidMount() {
this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window));
const query: IGetUsersparams = { const query: IGetUsersparams = {
include: { contact: true }, include: { contact: true },
}; };
const users = await Users.getInstance().get(query); Users.getInstance()
this.setState({ users }); .get(query)
} .then((users) => setUsers(users));
public override componentWillUnmount() { }, []);
this.onWindowResize();
}
private onOpenLeftSide() { const onSelectedBlock = (block: IBlock) => {
this.setState({ isLeftSideOpen: true }); router.push(Module.getInstance().get().modules.pages.Users.pages.UsersInformations.props.path.replace("[uid]", block.id));
} };
private onCloseLeftSide() { return (
if (!this.state.leftSideCanBeClosed) return; <DefaultDashboardWithList
this.setState({ isLeftSideOpen: false }); {...props}
} onSelectedBlock={onSelectedBlock}
blocks={
private onResize(window: Window) { users
if (window.innerWidth > 1023) { ? users.map((user) => ({
if (!this.state.leftSideCanBeClosed) return; id: user.uid!,
this.setState({ leftSideCanBeClosed: false }); primaryText: user.contact?.first_name + " " + user.contact?.last_name,
} isActive: user.uid === userUid,
this.setState({ leftSideCanBeClosed: true }); secondaryText: user.contact?.email,
}))
: []
} }
/>
);
} }

View File

@ -208,7 +208,7 @@ export default function CollaboratorInformations(props: IProps) {
options={availableRoles.filter((role) => { options={availableRoles.filter((role) => {
return role.label !== "admin"; return role.label !== "admin";
})} })}
onSelect={handleRoleChange} onSelectionChange={handleRoleChange}
selectedOption={selectedOption} selectedOption={selectedOption}
/> />
</div> </div>

View File

@ -3,9 +3,9 @@ import PenICon from "@Assets/Icons/pen.svg";
import DeedTypes from "@Front/Api/LeCoffreApi/Admin/DeedTypes/DeedTypes"; import DeedTypes from "@Front/Api/LeCoffreApi/Admin/DeedTypes/DeedTypes";
import DocumentTypes from "@Front/Api/LeCoffreApi/Admin/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Admin/DocumentTypes/DocumentTypes";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld"; import AutocompleteMultiSelectField from "@Front/Components/DesignSystem/Form/AutocompleteMultiSelectField";
import MultiSelect from "@Front/Components/DesignSystem/MultiSelect";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
import Tooltip from "@Front/Components/DesignSystem/ToolTip"; import Tooltip from "@Front/Components/DesignSystem/ToolTip";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
@ -17,7 +17,6 @@ import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { MultiValue } from "react-select";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
@ -28,7 +27,7 @@ export default function DeedTypesInformations(props: IProps) {
const [deedTypeSelected, setDeedTypeSelected] = useState<DeedType | null>(null); const [deedTypeSelected, setDeedTypeSelected] = useState<DeedType | null>(null);
const [availableDocuments, setAvailableDocuments] = useState<DocumentType[]>([]); const [availableDocuments, setAvailableDocuments] = useState<DocumentType[]>([]);
const [selectedDocuments, setSelectedDocuments] = useState<IOptionOld[]>([]); const [selectedDocuments, setSelectedDocuments] = useState<IOption[]>([]);
const [isDeleteModalOpened, setIsDeleteModalOpened] = useState<boolean>(false); const [isDeleteModalOpened, setIsDeleteModalOpened] = useState<boolean>(false);
const [isSaveModalOpened, setIsSaveModalOpened] = useState<boolean>(false); const [isSaveModalOpened, setIsSaveModalOpened] = useState<boolean>(false);
@ -71,11 +70,11 @@ export default function DeedTypesInformations(props: IProps) {
setDeedTypeSelected(deedType); setDeedTypeSelected(deedType);
if (!deedType.document_types) return; if (!deedType.document_types) return;
const documentsOptions: IOptionOld[] = deedType.document_types const documentsOptions: IOption[] = deedType.document_types
?.map((documentType) => { ?.map((documentType) => {
return { return {
label: documentType.name, label: documentType.name,
value: documentType.uid, id: documentType.uid ?? "",
}; };
}) })
.sort((a, b) => a.label.localeCompare(b.label)); .sort((a, b) => a.label.localeCompare(b.label));
@ -101,19 +100,19 @@ export default function DeedTypesInformations(props: IProps) {
const saveDocumentTypes = useCallback(async () => { const saveDocumentTypes = useCallback(async () => {
await DeedTypes.getInstance().put(deedTypeUid as string, { await DeedTypes.getInstance().put(deedTypeUid as string, {
uid: deedTypeUid as string, uid: deedTypeUid as string,
document_types: selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.value as string })), document_types: selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.id as string })),
}); });
closeSaveModal(); closeSaveModal();
}, [closeSaveModal, deedTypeUid, selectedDocuments]); }, [closeSaveModal, deedTypeUid, selectedDocuments]);
const onDocumentChangeHandler = useCallback((values: MultiValue<IOptionOld>) => { const onDocumentChangeHandler = useCallback((options: IOption[] | null) => {
setSelectedDocuments(values as IOptionOld[]); options && setSelectedDocuments(options);
}, []); }, []);
const formattedOptions: IOptionOld[] = availableDocuments const formattedOptions: IOption[] = availableDocuments
.map((document) => ({ .map((document) => ({
label: document.name, label: document.name,
value: document.uid, id: document.uid ?? "",
})) }))
.sort((a, b) => a.label.localeCompare(b.label)); .sort((a, b) => a.label.localeCompare(b.label));
@ -161,11 +160,11 @@ export default function DeedTypesInformations(props: IProps) {
<Tooltip text="Si vous ne trouvez pas le document que vous souhaitez dans la liste, cliquez sur « Modifier la liste des documents » pour créer ce type de document à la liste" /> <Tooltip text="Si vous ne trouvez pas le document que vous souhaitez dans la liste, cliquez sur « Modifier la liste des documents » pour créer ce type de document à la liste" />
</div> </div>
<div className={classes["documents"]}> <div className={classes["documents"]}>
<MultiSelect <AutocompleteMultiSelectField
label="Sélectionner des documents"
options={formattedOptions} options={formattedOptions}
placeholder="Cliquez pour sélectionner des documents" onSelectionChange={onDocumentChangeHandler}
onChange={onDocumentChangeHandler} selectedOptions={selectedDocuments}
defaultValue={selectedDocuments}
/> />
</div> </div>
<div className={classes["button-container"]}> <div className={classes["button-container"]}>

View File

@ -1,4 +1,6 @@
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert"; import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import Autocomplete from "@Front/Components/DesignSystem/Autocomplete";
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 CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
import Dropdown from "@Front/Components/DesignSystem/Dropdown"; import Dropdown from "@Front/Components/DesignSystem/Dropdown";
@ -31,7 +33,6 @@ import {
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 Autocomplete from "@Front/Components/DesignSystem/Autocomplete";
import CheckboxesInputElement from "@Front/Components/DesignSystem/CheckBox"; import CheckboxesInputElement from "@Front/Components/DesignSystem/CheckBox";
import RadioBox from "@Front/Components/DesignSystem/RadioBox"; import RadioBox from "@Front/Components/DesignSystem/RadioBox";
@ -86,6 +87,29 @@ export default function DesignSystem() {
<div className={classes["root"]}> <div className={classes["root"]}>
<div /> <div />
<div className={classes["components"]}> <div className={classes["components"]}>
<Typography typo={ETypo.TEXT_LG_BOLD}>Autocomplete Multi Select</Typography>
<AutocompleteMultiSelect
options={[
{
id: "1",
label: "Option 1",
},
{
id: "2",
label: "Option 2",
},
{
id: "3",
label: "Option 3",
},
{
id: "4",
label: { text: "Option 4", subtext: "Subtext" },
},
]}
label="Label"
/>
<Typography typo={ETypo.TEXT_LG_BOLD}>Autocomplete</Typography> <Typography typo={ETypo.TEXT_LG_BOLD}>Autocomplete</Typography>
<Autocomplete <Autocomplete
options={[ options={[
@ -106,7 +130,6 @@ export default function DesignSystem() {
label: { text: "Option 4", subtext: "Subtext" }, label: { text: "Option 4", subtext: "Subtext" },
}, },
]} ]}
placeholder="Placeholder"
label="Label" label="Label"
/> />
<Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown</Typography> <Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown</Typography>

View File

@ -3,10 +3,10 @@
.root { .root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100%;
align-items: flex-start; align-items: flex-start;
width: fit-content; width: 472px;
margin: auto;
margin-top: 24px;
.back-arrow { .back-arrow {
margin-bottom: 24px; margin-bottom: 24px;
} }
@ -22,7 +22,6 @@
.form { .form {
width: 100%; width: 100%;
.button-container { .button-container {
width: 100%; width: 100%;
display: flex; display: flex;
@ -42,12 +41,12 @@
margin-left: 0; margin-left: 0;
margin-top: 12px; margin-top: 12px;
>* { > * {
flex: 1; flex: 1;
} }
} }
>* { > * {
width: 100%; width: 100%;
} }
} }

View File

@ -1,237 +1,56 @@
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers"; import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import AutocompleteMultiSelect from "@Front/Components/DesignSystem/AutocompleteMultiSelect";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld"; import TextField from "@Front/Components/DesignSystem/Form/TextField";
import MultiSelect from "@Front/Components/DesignSystem/MultiSelect";
import RadioBox from "@Front/Components/DesignSystem/RadioBox"; import RadioBox from "@Front/Components/DesignSystem/RadioBox";
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";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { ValidationError } from "class-validator";
import { ECivility } from "le-coffre-resources/dist/Customer/Contact"; import { ECivility } from "le-coffre-resources/dist/Customer/Contact";
import { Contact, Customer, OfficeFolder } from "le-coffre-resources/dist/Notary"; import { Contact, Customer, OfficeFolder } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
import { NextRouter, useRouter } from "next/router"; import { useRouter } from "next/router";
import backgroundImage from "@Assets/images/background_refonte.svg";
import BasePage from "../../Base";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import TextField from "@Front/Components/DesignSystem/Form/TextField"; import { useCallback, useEffect, useState } from "react";
import { ValidationError } from "class-validator"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
enum ESelectedOption { enum ESelectedOption {
EXISTING_CUSTOMER = "existing_customer", EXISTING_CUSTOMER = "existing_customer",
NEW_CUSTOMER = "new_customer", NEW_CUSTOMER = "new_customer",
} }
type IProps = {}; type IProps = {};
type IState = {
selectedOption: ESelectedOption;
availableCustomers: Customer[];
selectedCustomers: readonly IOptionOld[];
existingCustomers: IOptionOld[];
isLoaded: boolean;
validationError: ValidationError[];
};
type IPropsClass = IProps & { export default function AddClientToFolder(props: IProps) {
selectedFolderUid: string; const router = useRouter();
router: NextRouter; let { folderUid } = router.query;
};
class AddClientToFolderClass extends BasePage<IPropsClass, IState> {
constructor(props: IPropsClass) {
super(props);
this.state = {
selectedOption: ESelectedOption.EXISTING_CUSTOMER,
availableCustomers: [],
selectedCustomers: [],
existingCustomers: [],
isLoaded: false,
validationError: [],
};
this.onExistingClientSelected = this.onExistingClientSelected.bind(this);
this.onNewClientSelected = this.onNewClientSelected.bind(this);
this.onMutiSelectChange = this.onMutiSelectChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
public override render(): JSX.Element {
const selectOptions: IOptionOld[] = this.getSelectedOptions();
const backwardPath = Module.getInstance() const [selectedOption, setSelectedOption] = useState<ESelectedOption>(ESelectedOption.EXISTING_CUSTOMER);
.get() const [availableCustomers, setAvailableCustomers] = useState<Customer[]>([]);
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.selectedFolderUid); const [selectedCustomers, setSelectedCustomers] = useState<IOption[]>([]);
return ( const [existingCustomers, setExistingCustomers] = useState<IOption[]>([]);
<DefaultNotaryDashboard title={"Ajouter client(s)"}> const [isLoaded, setIsLoaded] = useState<boolean>(false);
<div className={classes["root"]}> const [validationError, setValidationError] = useState<ValidationError[]>([]);
<div className={classes["back-arrow"]}>
<BackArrow url={backwardPath} />
</div>
<Typography typo={ETypo.TITLE_H1}>Associer un ou plusieurs client(s)</Typography>
{this.state.isLoaded && (
<>
<div className={classes["radiobox-container"]}>
{this.state.availableCustomers.length !== 0 && (
<RadioBox
name="client"
onChange={this.onExistingClientSelected}
checked={this.state.selectedOption === "existing_customer"}
value={"existing client"}>
<Typography typo={ETypo.TEXT_LG_REGULAR}>Client existant</Typography>
</RadioBox>
)}
<RadioBox const onFormSubmit = useCallback(
name="client" async (
onChange={this.onNewClientSelected}
checked={this.state.selectedOption === "new_customer"}
value={"new client"}>
<Typography typo={ETypo.TEXT_LG_REGULAR}>Nouveau client</Typography>
</RadioBox>
</div>
<Form className={classes["form"]} onSubmit={this.onFormSubmit}>
{this.state.selectedOption === "existing_customer" && (
<div className={classes["existing-client"]}>
<MultiSelect
options={selectOptions}
placeholder="Clients"
onChange={this.onMutiSelectChange}
defaultValue={this.state.selectedCustomers}
/>
<div className={classes["button-container"]}>
<Link href={backwardPath} className={classes["cancel-button"]}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
Annuler
</Button>
</Link>
<Button type="submit">Associer au dossier</Button>
</div>
</div>
)}
{this.state.selectedOption === "new_customer" && (
<div className={classes["new-client"]}>
<TextField
name="last_name"
placeholder="Nom"
validationError={this.state.validationError.find((error) => error.property === "last_name")}
/>
<TextField
name="first_name"
placeholder="Prénom"
validationError={this.state.validationError.find((error) => error.property === "first_name")}
/>
<TextField
name="email"
placeholder="E-mail"
validationError={this.state.validationError.find((error) => error.property === "email")}
/>
<TextField
name="cell_phone_number"
placeholder="Numéro de téléphone"
validationError={this.state.validationError.find(
(error) => error.property === "cell_phone_number",
)}
/>
<div className={classes["button-container"]}>
<Link href={backwardPath} className={classes["cancel-button"]}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
Annuler
</Button>
</Link>
<Button type="submit">Associer au dossier</Button>
</div>
</div>
)}
</Form>
</>
)}
</div>
</DefaultNotaryDashboard>
);
}
public override async componentDidMount() {
const query = {};
const availableCustomers = await Customers.getInstance().get(query);
let preExistingCustomers: IOptionOld[] | undefined = await this.getFolderPreSelectedCustomers(this.props.selectedFolderUid);
const existingCustomers = preExistingCustomers ?? [];
existingCustomers.forEach((customer) => {
const index = availableCustomers.findIndex((availableCustomer) => availableCustomer.uid === customer.value);
if (index !== -1) availableCustomers.splice(index, 1);
});
let selectedOption = ESelectedOption.EXISTING_CUSTOMER;
if (availableCustomers.length === 0) {
selectedOption = ESelectedOption.NEW_CUSTOMER;
}
this.setState({ availableCustomers, existingCustomers, isLoaded: true, selectedOption });
}
private async getFolderPreSelectedCustomers(folderUid: string): Promise<IOptionOld[] | undefined> {
const query = {
q: {
customers: {
include: {
contact: true,
},
},
},
};
let preExistingCustomers: IOptionOld[] = [];
try {
const folder = await Folders.getInstance().getByUid(folderUid, query);
preExistingCustomers = folder.customers!.map((customer) => {
return {
label: customer.contact?.first_name + " " + customer.contact?.last_name,
value: customer.uid,
};
});
} catch (error) {
this.props.router.push(Module.getInstance().get().modules.pages["404"].props.path);
return;
}
return preExistingCustomers;
}
private getSelectedOptions(): IOptionOld[] {
let options = this.state.availableCustomers?.map((customer) => {
return {
label: customer.contact?.first_name + " " + customer.contact?.last_name,
value: customer.uid,
};
});
if (!options) options = [];
return options;
}
private onMutiSelectChange(selectedCustomers: readonly IOptionOld[]): void {
this.setState({ selectedCustomers });
}
private onExistingClientSelected(): void {
this.setState({ selectedOption: ESelectedOption.EXISTING_CUSTOMER });
}
private onNewClientSelected(): void {
this.setState({ selectedOption: ESelectedOption.NEW_CUSTOMER });
}
private async onFormSubmit(
e: React.FormEvent<HTMLFormElement> | null, e: React.FormEvent<HTMLFormElement> | null,
values: { values: {
[key: string]: any; [key: string]: any;
}, },
) { ) => {
values["civility"] = ECivility.MALE; // TODO: should maybe be deleted later or added to the form values["civility"] = ECivility.MALE; // TODO: should maybe be deleted later or added to the form
const allCustomersToLink = this.state.selectedCustomers.concat(this.state.existingCustomers); const allCustomersToLink = selectedCustomers.concat(existingCustomers);
let customersToLink: Partial<Customer>[] = allCustomersToLink.map((customer) => { let customersToLink: Partial<Customer>[] = allCustomersToLink.map((customer) => {
return Customer.hydrate<Customer>({ uid: customer.value as string }); return Customer.hydrate<Customer>({ uid: customer.id as string });
}) as Partial<Customer>[]; }) as Partial<Customer>[];
if (this.state.selectedOption === "new_customer") { if (selectedOption === "new_customer") {
try { try {
// remove every space from the phone number // remove every space from the phone number
values["cell_phone_number"] = values["cell_phone_number"].replace(/\s/g, ""); values["cell_phone_number"] = values["cell_phone_number"].replace(/\s/g, "");
@ -247,9 +66,7 @@ class AddClientToFolderClass extends BasePage<IPropsClass, IState> {
const contactToCreate = Contact.hydrate<Customer>(values); const contactToCreate = Contact.hydrate<Customer>(values);
await contactToCreate.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false }); await contactToCreate.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false });
} catch (validationErrors) { } catch (validationErrors) {
this.setState({ setValidationError(validationErrors as ValidationError[]);
validationError: validationErrors as ValidationError[],
});
return; return;
} }
@ -261,9 +78,7 @@ class AddClientToFolderClass extends BasePage<IPropsClass, IState> {
customersToLink?.push({ uid: customer.uid } as Partial<Customer>); customersToLink?.push({ uid: customer.uid } as Partial<Customer>);
} catch (backError) { } catch (backError) {
if (!Array.isArray(backError)) return; if (!Array.isArray(backError)) return;
this.setState({ setValidationError(backError as ValidationError[]);
validationError: backError as ValidationError[],
});
return; return;
} }
} }
@ -274,15 +89,178 @@ class AddClientToFolderClass extends BasePage<IPropsClass, IState> {
return Customer.hydrate<Customer>(customer); return Customer.hydrate<Customer>(customer);
}), }),
}); });
await Folders.getInstance().put(this.props.selectedFolderUid, body); await Folders.getInstance().put(folderUid as string, body);
this.props.router.push(`/folders/${this.props.selectedFolderUid}`); router.push(`/folders/${folderUid}`);
} }
} },
} [existingCustomers, folderUid, router, selectedCustomers, selectedOption],
);
export default function AddClientToFolder(props: IProps) { const getFolderPreSelectedCustomers = useCallback(
const router = useRouter(); async (folderUid: string): Promise<IOption[] | undefined> => {
let { folderUid } = router.query; const query = {
folderUid = folderUid as string; q: {
return <AddClientToFolderClass {...props} selectedFolderUid={folderUid} router={router} />; customers: {
include: {
contact: true,
},
},
},
};
let preExistingCustomers: IOption[] = [];
try {
const folder = await Folders.getInstance().getByUid(folderUid, query);
preExistingCustomers = folder.customers!.map((customer) => {
return {
label: customer.contact?.first_name + " " + customer.contact?.last_name,
id: customer.uid ?? "",
};
});
} catch (error) {
router.push(Module.getInstance().get().modules.pages["404"].props.path);
return;
}
return preExistingCustomers;
},
[router],
);
const loadCustomers = useCallback(async () => {
const query = {};
const availableCustomers = await Customers.getInstance().get(query);
let preExistingCustomers: IOption[] | undefined = await getFolderPreSelectedCustomers(folderUid as string);
const existingCustomers = preExistingCustomers ?? [];
existingCustomers.forEach((customer) => {
const index = availableCustomers.findIndex((availableCustomer) => availableCustomer.uid === customer.id);
if (index !== -1) availableCustomers.splice(index, 1);
});
let selectedOption = ESelectedOption.EXISTING_CUSTOMER;
if (availableCustomers.length === 0) {
selectedOption = ESelectedOption.NEW_CUSTOMER;
}
setAvailableCustomers(availableCustomers);
setExistingCustomers(existingCustomers);
setIsLoaded(true);
setSelectedOption(selectedOption);
}, [folderUid, getFolderPreSelectedCustomers]);
const getSelectedOptions = useCallback((): IOption[] => {
let options = availableCustomers?.map((customer) => {
return {
label: customer.contact?.first_name + " " + customer.contact?.last_name,
id: customer.uid ?? "",
};
});
if (!options) options = [];
return options;
}, [availableCustomers]);
const onMutiSelectChange = (selectedCustomers: IOption[] | null): void => {
if (!selectedCustomers) return;
setSelectedCustomers(selectedCustomers);
};
const onExistingClientSelected = (): void => setSelectedOption(ESelectedOption.EXISTING_CUSTOMER);
const onNewClientSelected = (): void => setSelectedOption(ESelectedOption.NEW_CUSTOMER);
useEffect(() => {
loadCustomers();
}, [loadCustomers]);
const selectOptions: IOption[] = getSelectedOptions();
const backwardPath = Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string);
return (
<DefaultDoubleSidePage title={"Ajouter client(s)"} image={backgroundImage} showHeader>
<div className={classes["root"]}>
<div className={classes["back-arrow"]}>
<BackArrow url={backwardPath} />
</div>
<Typography typo={ETypo.TITLE_H1}>Associer un ou plusieurs client(s)</Typography>
{isLoaded && (
<>
<div className={classes["radiobox-container"]}>
{availableCustomers.length !== 0 && (
<RadioBox
name="client"
onChange={onExistingClientSelected}
checked={selectedOption === "existing_customer"}
value={"existing client"}>
<Typography typo={ETypo.TEXT_LG_REGULAR}>Client existant</Typography>
</RadioBox>
)}
<RadioBox
name="client"
onChange={onNewClientSelected}
checked={selectedOption === "new_customer"}
value={"new client"}>
<Typography typo={ETypo.TEXT_LG_REGULAR}>Nouveau client</Typography>
</RadioBox>
</div>
<Form className={classes["form"]} onSubmit={onFormSubmit}>
{selectedOption === "existing_customer" && (
<div className={classes["existing-client"]}>
<AutocompleteMultiSelect
label="Clients"
options={selectOptions}
selectedOptions={selectedCustomers}
onSelectionChange={onMutiSelectChange}
/>
<div className={classes["button-container"]}>
<Link href={backwardPath} className={classes["cancel-button"]}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
Annuler
</Button>
</Link>
<Button type="submit">Associer au dossier</Button>
</div>
</div>
)}
{selectedOption === "new_customer" && (
<div className={classes["new-client"]}>
<TextField
name="last_name"
placeholder="Nom"
validationError={validationError.find((error) => error.property === "last_name")}
/>
<TextField
name="first_name"
placeholder="Prénom"
validationError={validationError.find((error) => error.property === "first_name")}
/>
<TextField
name="email"
placeholder="E-mail"
validationError={validationError.find((error) => error.property === "email")}
/>
<TextField
name="cell_phone_number"
placeholder="Numéro de téléphone"
validationError={validationError.find((error) => error.property === "cell_phone_number")}
/>
<div className={classes["button-container"]}>
<Link href={backwardPath} className={classes["cancel-button"]}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
Annuler
</Button>
</Link>
<Button type="submit">Associer au dossier</Button>
</div>
</div>
)}
</Form>
</>
)}
</div>
</DefaultDoubleSidePage>
);
} }

View File

@ -1,14 +1,13 @@
import Deeds from "@Front/Api/LeCoffreApi/Notary/Deeds/Deeds"; import Deeds from "@Front/Api/LeCoffreApi/Notary/Deeds/Deeds";
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld"; import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
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 MultiSelect from "@Front/Components/DesignSystem/MultiSelect";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
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";
import { MultiValue } from "react-select";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
@ -24,20 +23,20 @@ export default function ParameterDocuments(props: IProps) {
const [addOrEditDocument, setAddOrEditDocument] = useState<"add" | "edit">("edit"); const [addOrEditDocument, setAddOrEditDocument] = useState<"add" | "edit">("edit");
const [selectedDocuments, setSelectedDocuments] = useState<IOptionOld[]>([]); const [selectedDocuments, setSelectedDocuments] = useState<IOption[]>([]);
const [formattedOptions, setFormattedOptions] = useState<IOptionOld[]>([]); const [formattedOptions, setFormattedOptions] = useState<IOption[]>([]);
const getAvailableDocuments = useCallback(async () => { const getAvailableDocuments = useCallback(async () => {
const documents = await DocumentTypes.getInstance().get({}); const documents = await DocumentTypes.getInstance().get({});
const formattedOptions: IOptionOld[] = documents const formattedOptions: IOption[] = documents
.filter((document) => { .filter((document) => {
return !props.folder.deed?.document_types?.some((documentType) => documentType.uid === document.uid); return !props.folder.deed?.document_types?.some((documentType) => documentType.uid === document.uid);
}) })
.map((document) => { .map((document) => {
return { return {
label: document.name, label: document.name,
value: document.uid, id: document.uid ?? "",
}; };
}); });
formattedOptions.sort((a, b) => (a.label > b.label ? 1 : -1)); formattedOptions.sort((a, b) => (a.label > b.label ? 1 : -1));
@ -52,8 +51,8 @@ export default function ParameterDocuments(props: IProps) {
setDocumentName(event.target.value); setDocumentName(event.target.value);
}; };
const onDocumentChangeHandler = useCallback((values: MultiValue<IOptionOld>) => { const onDocumentChangeHandler = useCallback((options: IOption[] | null) => {
setSelectedDocuments(values as IOptionOld[]); options && setSelectedDocuments(options);
}, []); }, []);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
@ -93,7 +92,7 @@ export default function ParameterDocuments(props: IProps) {
await Deeds.getInstance().put(props.folder.deed?.uid!, { await Deeds.getInstance().put(props.folder.deed?.uid!, {
document_types: [ document_types: [
...oldDocumentsType, ...oldDocumentsType,
...selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.value as string })), ...selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.id as string })),
], ],
}); });
@ -128,9 +127,21 @@ export default function ParameterDocuments(props: IProps) {
confirmText={"Ajouter"}> confirmText={"Ajouter"}>
<div className={classes["add-document-form-container"]}> <div className={classes["add-document-form-container"]}>
<div className={classes["radiobox-container"]}> <div className={classes["radiobox-container"]}>
<RadioBox name="document" onChange={selectEditMode} checked={addOrEditDocument === "edit"} value={"existing client"} label="Document existant"/> <RadioBox
name="document"
onChange={selectEditMode}
checked={addOrEditDocument === "edit"}
value={"existing client"}
label="Document existant"
/>
<RadioBox name="document" onChange={selectAddMode} checked={addOrEditDocument === "add"} value={"new client"} label="Créer un document"> <RadioBox
name="document"
onChange={selectAddMode}
checked={addOrEditDocument === "add"}
value={"new client"}
label="Créer un document"
/>
</div> </div>
{addOrEditDocument === "add" && ( {addOrEditDocument === "add" && (
<> <>
@ -143,11 +154,11 @@ export default function ParameterDocuments(props: IProps) {
</> </>
)} )}
{addOrEditDocument === "edit" && ( {addOrEditDocument === "edit" && (
<MultiSelect <AutocompleteMultiSelectField
label="Sélectionner des documents"
options={formattedOptions} options={formattedOptions}
placeholder="Cliquez pour sélectionner des documents" onSelectionChange={onDocumentChangeHandler}
onChange={onDocumentChangeHandler} selectedOptions={selectedDocuments}
defaultValue={selectedDocuments}
/> />
)} )}
</div> </div>

View File

@ -1,6 +1,8 @@
@import "@Themes/constants.scss"; @import "@Themes/constants.scss";
.root { .root {
margin: 24px auto;
width: 600px;
.title { .title {
margin-top: 24px; margin-top: 24px;
} }

View File

@ -1,147 +1,71 @@
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents"; import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
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, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import CheckBox from "@Front/Components/DesignSystem/CheckBox"; import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import BackArrow from "@Front/Components/Elements/BackArrow"; import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { NextRouter, useRouter } from "next/router"; import { useRouter } from "next/router";
import React from "react"; import React, { useCallback, useEffect, useState } from "react";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import BasePage from "../../Base";
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 { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
import backgroundImage from "@Assets/images/background_refonte.svg";
type IProps = {}; export default function AskDocuments() {
type IPropsClass = IProps & { const router = useRouter();
router: NextRouter; let { folderUid, customerUid } = router.query;
folderUid: string; const [isCreateDocumentModalVisible, setIsCreateDocumentModalVisible] = useState<boolean>(false);
customerUid: string; const [documentTypes, setDocumentTypes] = useState<IOptionOld[]>([]);
}; const [folder, setFolder] = useState<OfficeFolder | null>(null);
type IState = {
isCreateDocumentModalVisible: boolean;
documentTypes: IOptionOld[];
folder: OfficeFolder | null;
};
class AskDocumentsClass extends BasePage<IPropsClass, IState> { const closeModal = () => setIsCreateDocumentModalVisible(false);
public constructor(props: IPropsClass) { const openModal = () => setIsCreateDocumentModalVisible(true);
super(props);
this.state = { const onFormSubmit = useCallback(
isCreateDocumentModalVisible: false, async (
documentTypes: [], e: React.FormEvent<HTMLFormElement> | null,
folder: null, values: {
}; [key: string]: any;
},
this.onFormSubmit = this.onFormSubmit.bind(this); ) => {
this.closeModal = this.closeModal.bind(this);
this.openModal = this.openModal.bind(this);
}
public override render(): JSX.Element {
const backUrl = Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.folderUid);
return (
<DefaultNotaryDashboard title={"Demander des documents"} onSelectedFolder={() => {}}>
<div className={classes["root"]}>
<BackArrow url={backUrl} />
<Typography typo={ETypo.DISPLAY_LARGE} color={ETypoColor.COLOR_GENERIC_BLACK} className={classes["title"]}>
Demander des documents
</Typography>
<Form onSubmit={this.onFormSubmit}>
<div className={classes["form-container"]}>
<div className={classes["checkbox-container"]}>
{this.state.documentTypes.map((documentType) => {
if (documentType.description && documentType.description.length > 1) {
return (
<CheckBox
name="document_types"
toolTip={documentType.description}
option={documentType}
key={documentType.value as string}
/>
);
}
return <CheckBox name="document_types" option={documentType} key={documentType.value as string} />;
})}
</div>
<div className={classes["add-document-container"]}>
<Button
rightIcon={<PlusIcon style={{ transform: "rotate(180deg)", width: "22px", height: "22px" }} />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.openModal}>
Ajouter un document
</Button>
</div>
<div className={classes["buttons-container"]}>
<a href={backUrl}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} onClick={this.cancel}>
Annuler
</Button>
</a>
<Button type="submit">Valider</Button>
</div>
</div>
</Form>
</div>
{this.state.folder && (
<ParameterDocuments
folder={this.state.folder}
closeModal={this.closeModal}
isCreateDocumentModalVisible={this.state.isCreateDocumentModalVisible}
/>
)}
</DefaultNotaryDashboard>
);
}
public override async componentDidMount(): Promise<void> {
this.loadData();
}
private cancel() {}
private async loadData() {
try { try {
const folder = await Folders.getInstance().getByUid(this.props.folderUid, { const documentAsked: [] = values["document_types"] as [];
q: { for (let i = 0; i < documentAsked.length; i++) {
deed: { await Documents.getInstance().post({
include: { folder: {
document_types: true, uid: folderUid,
},
},
office: true,
documents: {
include: {
depositor: true,
document_type: true,
}, },
depositor: {
uid: customerUid,
}, },
document_type: {
uid: documentAsked[i],
}, },
}); });
if (!folder) return; }
this.setState({
folder, router.push(
documentTypes: await this.getAvailableDocuments(folder), Module.getInstance()
}); .get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} },
[customerUid, folderUid, router],
);
private async getAvailableDocuments(folder: OfficeFolder): Promise<IOptionOld[]> { const getAvailableDocuments = useCallback(
async (folder: OfficeFolder): Promise<IOptionOld[]> => {
// 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! === this.props.customerUid!) .documents!.filter((document) => document.depositor!.uid! === customerUid!)
.map((document) => { .map((document) => {
return document.document_type!.uid!; return document.document_type!.uid!;
}); });
@ -168,58 +92,91 @@ class AskDocumentsClass extends BasePage<IPropsClass, IState> {
documentTypesOptions.sort((a, b) => (a.label > b.label ? 1 : -1)); documentTypesOptions.sort((a, b) => (a.label > b.label ? 1 : -1));
return documentTypesOptions; return documentTypesOptions;
}
private openModal() {
this.setState({
isCreateDocumentModalVisible: true,
});
}
private closeModal() {
this.loadData();
this.setState({
isCreateDocumentModalVisible: false,
});
}
private async onFormSubmit(
e: React.FormEvent<HTMLFormElement> | null,
values: {
[key: string]: any;
}, },
) { [customerUid],
try {
const documentAsked: [] = values["document_types"] as [];
for (let i = 0; i < documentAsked.length; i++) {
await Documents.getInstance().post({
folder: {
uid: this.props.folderUid,
},
depositor: {
uid: this.props.customerUid,
},
document_type: {
uid: documentAsked[i],
},
});
}
this.props.router.push(
Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.folderUid),
); );
const loadData = useCallback(async () => {
try {
const folder = await Folders.getInstance().getByUid(folderUid as string, {
q: {
deed: {
include: {
document_types: true,
},
},
office: true,
documents: {
include: {
depositor: true,
document_type: true,
},
},
},
});
if (!folder) return;
setFolder(folder);
setDocumentTypes(await getAvailableDocuments(folder));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }, [folderUid, getAvailableDocuments]);
}
export default function AskDocuments(props: IProps) { useEffect(() => {
const router = useRouter(); loadData();
let { folderUid, customerUid } = router.query; }, [loadData]);
folderUid = folderUid as string;
customerUid = customerUid as string; const backUrl = Module.getInstance()
return <AskDocumentsClass folderUid={folderUid} customerUid={customerUid} router={router} />; .get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string);
return (
<DefaultDoubleSidePage title={"Demander des documents"} image={backgroundImage} showHeader>
<div className={classes["root"]}>
<BackArrow url={backUrl} />
<Typography typo={ETypo.DISPLAY_LARGE} color={ETypoColor.COLOR_GENERIC_BLACK} className={classes["title"]}>
Demander des documents
</Typography>
<Form onSubmit={onFormSubmit}>
<div className={classes["form-container"]}>
<div className={classes["checkbox-container"]}>
{documentTypes.map((documentType) => {
if (documentType.description && documentType.description.length > 1) {
return (
<CheckBox
name="document_types"
toolTip={documentType.description}
option={documentType}
key={documentType.value as string}
/>
);
}
return <CheckBox name="document_types" option={documentType} key={documentType.value as string} />;
})}
</div>
<div className={classes["add-document-container"]}>
<Button
rightIcon={<PlusIcon style={{ transform: "rotate(180deg)", width: "22px", height: "22px" }} />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}
onClick={openModal}>
Ajouter un document
</Button>
</div>
<div className={classes["buttons-container"]}>
<a href={backUrl}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
Annuler
</Button>
</a>
<Button type="submit">Valider</Button>
</div>
</div>
</Form>
</div>
{folder && (
<ParameterDocuments folder={folder} closeModal={closeModal} isCreateDocumentModalVisible={isCreateDocumentModalVisible} />
)}
</DefaultDoubleSidePage>
);
} }

View File

@ -5,11 +5,10 @@ import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
import Button from "@Front/Components/DesignSystem/Button"; import Button from "@Front/Components/DesignSystem/Button";
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption"; import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import AutocompleteMultiSelectField from "@Front/Components/DesignSystem/Form/AutocompleteMultiSelectField";
import SelectField from "@Front/Components/DesignSystem/Form/SelectField"; import SelectField from "@Front/Components/DesignSystem/Form/SelectField";
import { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
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 MultiSelect from "@Front/Components/DesignSystem/MultiSelect";
import RadioBox from "@Front/Components/DesignSystem/RadioBox"; import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import BackArrow from "@Front/Components/Elements/BackArrow"; import BackArrow from "@Front/Components/Elements/BackArrow";
@ -20,8 +19,7 @@ import { Deed, Office, OfficeFolder } from "le-coffre-resources/dist/Notary";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
import { DeedType } from "le-coffre-resources/dist/Notary"; import { DeedType } from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { MultiValue } from "react-select";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
@ -89,12 +87,14 @@ export default function CreateFolder(): JSX.Element {
const radioOnChange = (e: React.ChangeEvent<HTMLInputElement>) => const radioOnChange = (e: React.ChangeEvent<HTMLInputElement>) =>
setFolderAccessType(e.target.value as "whole_office" | "select_collaborators"); setFolderAccessType(e.target.value as "whole_office" | "select_collaborators");
const onSelectedCollaboratorsChange = (values: MultiValue<IOptionOld>) => { const onSelectedCollaboratorsChange = useCallback(
const selectedCollaborators = availableCollaborators.filter((collaborator) => (options: IOption[] | null) => {
values.some((value) => value.value === collaborator.uid), if (!options) return;
const collaborators = availableCollaborators.filter((collaborator) => options.find((option) => option.id === collaborator.uid));
setSelectedCollaborators(collaborators);
},
[availableCollaborators],
); );
setSelectedCollaborators(selectedCollaborators);
};
/** /**
* UseEffect * UseEffect
@ -185,19 +185,17 @@ export default function CreateFolder(): JSX.Element {
</div> </div>
{folderAccessType === "select_collaborators" && ( {folderAccessType === "select_collaborators" && (
<div className={classes["collaborators-container"]}> <div className={classes["collaborators-container"]}>
<MultiSelect <AutocompleteMultiSelectField
options={ label="Sélectionner les collaborateurs"
availableCollaborators.map((collaborator) => ({ options={availableCollaborators.map((collaborator) => ({
label: collaborator.contact?.last_name.concat(" ", collaborator.contact.first_name), label: collaborator.contact?.last_name.concat(" ", collaborator.contact.first_name) ?? "",
value: collaborator.uid, id: collaborator.uid ?? "",
})) as IOptionOld[]
}
defaultValue={selectedCollaborators.map((collaborator) => ({
label: collaborator.contact?.last_name.concat(" ", collaborator.contact.first_name) as string,
value: collaborator.uid!,
}))} }))}
onChange={onSelectedCollaboratorsChange} selectedOptions={selectedCollaborators.map((collaborator) => ({
placeholder="Sélectionner les collaborateurs" label: collaborator.contact?.last_name.concat(" ", collaborator.contact.first_name) ?? "",
id: collaborator.uid ?? "",
}))}
onSelectionChange={onSelectedCollaboratorsChange}
validationError={validationError.find((error) => error.property === "stakeholders")} validationError={validationError.find((error) => error.property === "stakeholders")}
/> />
</div> </div>

View File

@ -74,7 +74,7 @@ class UpdateClientClass extends BasePage<IPropsClass, IState> {
public override render(): JSX.Element { public override render(): JSX.Element {
return ( return (
<DefaultNotaryDashboard title={"Ajouter client(s)"} onSelectedFolder={this.onSelectedFolder}> <DefaultNotaryDashboard title={"Ajouter client(s)"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["back-arrow"]}> <div className={classes["back-arrow"]}>
<BackArrow url={this.backwardPath} /> <BackArrow url={this.backwardPath} />

View File

@ -2,9 +2,9 @@ import backgroundImage from "@Assets/images/background_refonte.svg";
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Users, { IGetUsersParams } from "@Front/Api/LeCoffreApi/Notary/Users/Users"; import Users, { IGetUsersParams } from "@Front/Api/LeCoffreApi/Notary/Users/Users";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import { IOptionOld } from "@Front/Components/DesignSystem/Form/SelectFieldOld"; import AutocompleteMultiSelectField from "@Front/Components/DesignSystem/Form/AutocompleteMultiSelectField";
import MultiSelect from "@Front/Components/DesignSystem/MultiSelect";
import RadioBox from "@Front/Components/DesignSystem/RadioBox"; import RadioBox from "@Front/Components/DesignSystem/RadioBox";
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";
@ -26,9 +26,8 @@ enum ERadioBoxValue {
export default function UpdateFolderCollaborators() { export default function UpdateFolderCollaborators() {
const router = useRouter(); const router = useRouter();
let { folderUid } = router.query; let { folderUid } = router.query;
const [availableCollaborators, setAvailableCollaborators] = useState<User[]>([]); const [availableCollaborators, setAvailableCollaborators] = useState<User[]>([]);
const [selectedCollaborators, setSelectedCollaborators] = useState<readonly IOptionOld[]>([]); const [selectedCollaborators, setSelectedCollaborators] = useState<IOption[]>([]);
const [defaultCheckedAllOffice, setDefaultCheckedAllOffice] = useState<boolean>(false); const [defaultCheckedAllOffice, setDefaultCheckedAllOffice] = useState<boolean>(false);
const [selectedOption, setSelectedOption] = useState<ERadioBoxValue>(ERadioBoxValue.SELECTION); const [selectedOption, setSelectedOption] = useState<ERadioBoxValue>(ERadioBoxValue.SELECTION);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
@ -40,9 +39,7 @@ export default function UpdateFolderCollaborators() {
try { try {
let collaboratorsUid: User[] = availableCollaborators; let collaboratorsUid: User[] = availableCollaborators;
if (selectedOption === ERadioBoxValue.SELECTION) { if (selectedOption === ERadioBoxValue.SELECTION) {
collaboratorsUid = selectedCollaborators.map((collaborator) => collaboratorsUid = selectedCollaborators.map((collaborator) => User.hydrate<User>({ uid: collaborator.id as string }));
User.hydrate<User>({ uid: collaborator.value as string }),
);
} }
await Folders.getInstance().put(folderUid, { stakeholders: collaboratorsUid }); await Folders.getInstance().put(folderUid, { stakeholders: collaboratorsUid });
router.push( router.push(
@ -61,8 +58,8 @@ export default function UpdateFolderCollaborators() {
setSelectedOption(ERadioBoxValue.ALL); setSelectedOption(ERadioBoxValue.ALL);
}, []); }, []);
const onChangeSelectedCollaborators = useCallback((selectedCollaborators: readonly IOptionOld[]) => { const onChangeSelectedCollaborators = useCallback((selectedCollaborators: IOption[] | null) => {
setSelectedCollaborators(selectedCollaborators); selectedCollaborators && setSelectedCollaborators(selectedCollaborators);
}, []); }, []);
const onSelectedOptionSpecific = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const onSelectedOptionSpecific = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
@ -86,10 +83,10 @@ export default function UpdateFolderCollaborators() {
let folder = null; let folder = null;
try { try {
folder = await Folders.getInstance().getByUid(folderUid, query); folder = await Folders.getInstance().getByUid(folderUid, query);
const preSelectedCollaborators: IOptionOld[] = folder.stakeholders!.map((collaborator) => { const preSelectedCollaborators: IOption[] = folder.stakeholders!.map((collaborator) => {
return { return {
label: collaborator.contact?.first_name + " " + collaborator.contact?.last_name, label: collaborator.contact?.first_name + " " + collaborator.contact?.last_name,
value: collaborator.uid, id: collaborator.uid ?? "",
}; };
}); });
@ -126,10 +123,10 @@ export default function UpdateFolderCollaborators() {
const foldersInformationPath = Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path; const foldersInformationPath = Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path;
const backwardPath = foldersInformationPath.replace("[folderUid]", folderUid as string); const backwardPath = foldersInformationPath.replace("[folderUid]", folderUid as string);
const selectOptions: IOptionOld[] = availableCollaborators.map((collaborator) => { const selectOptions: IOption[] = availableCollaborators.map((collaborator) => {
return { return {
label: collaborator.contact?.first_name + " " + collaborator.contact?.last_name, label: collaborator.contact?.first_name + " " + collaborator.contact?.last_name,
value: collaborator.uid, id: collaborator.uid ?? "",
}; };
}); });
@ -162,11 +159,11 @@ export default function UpdateFolderCollaborators() {
{selectedOption === ERadioBoxValue.SELECTION && ( {selectedOption === ERadioBoxValue.SELECTION && (
<div className={classes["sub-content"]}> <div className={classes["sub-content"]}>
<MultiSelect <AutocompleteMultiSelectField
onChange={onChangeSelectedCollaborators} label="Collaborateurs"
options={selectOptions} options={selectOptions}
placeholder="Collaborateurs" selectedOptions={selectedCollaborators}
defaultValue={selectedCollaborators} onSelectionChange={onChangeSelectedCollaborators}
validationError={validationError?.find((error) => error.property === "stakeholders")} validationError={validationError?.find((error) => error.property === "stakeholders")}
/> />
</div> </div>

View File

@ -6,10 +6,10 @@ import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNot
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import { DocumentIcon } from "@heroicons/react/24/outline"; import { DocumentIcon } from "@heroicons/react/24/outline";
import User, { OfficeFolder } from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useCallback, 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 Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
@ -17,14 +17,10 @@ import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
export default function Folder() { export default function Folder() {
const [_folder, setFolder] = useState<OfficeFolder | null>(null);
const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true); const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true);
const router = useRouter(); const router = useRouter();
const [activeUser, setActiveUser] = useState<User | null>(); const [activeUser, setActiveUser] = useState<User | null>();
const onSelectedFolder = useCallback((folder: OfficeFolder): void => {
setFolder(folder);
}, []);
useEffect(() => { useEffect(() => {
const decodedJwt = JwtService.getInstance().decodeJwt(); const decodedJwt = JwtService.getInstance().decodeJwt();
@ -59,7 +55,7 @@ export default function Folder() {
}, [router]); }, [router]);
return ( return (
<DefaultNotaryDashboard title={"Dossier"} onSelectedFolder={onSelectedFolder} mobileBackText={"Liste des dossiers"}> <DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["content"]}> <div className={classes["content"]}>
<div className={classes["title-container"]}> <div className={classes["title-container"]}>

View File

@ -39,7 +39,7 @@ class UpdateFolderMetadataClass extends BasePage<IProps, IState> {
.get() .get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.selectedFolderUid); .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.selectedFolderUid);
return ( return (
<DefaultNotaryDashboard title={"Ajouter client(s)"} onSelectedFolder={this.onSelectedFolder}> <DefaultNotaryDashboard title={"Ajouter client(s)"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["back-arrow"]}> <div className={classes["back-arrow"]}>
<BackArrow url={backwardPath} /> <BackArrow url={backwardPath} />

View File

@ -23,11 +23,7 @@ export default class FolderArchived extends BasePage<IProps, IState> {
// TODO: Message if the user has not created any folder yet // TODO: Message if the user has not created any folder yet
public override render(): JSX.Element { public override render(): JSX.Element {
return ( return (
<DefaultNotaryDashboard <DefaultNotaryDashboard title={"Dossier"} isArchived mobileBackText={"Liste des dossiers"}>
title={"Dossier"}
onSelectedFolder={this.onSelectedFolder}
isArchived
mobileBackText={"Liste des dossiers"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["no-folder-selected"]}> <div className={classes["no-folder-selected"]}>
<Typography typo={ETypo.TITLE_H1}>Informations du dossier</Typography> <Typography typo={ETypo.TITLE_H1}>Informations du dossier</Typography>

View File

@ -0,0 +1,11 @@
.pdf-viewer {
width: 100%;
height: 100%;
min-height: 100%;
min-width: 100%;
.no-pdf-text {
padding: 16px;
text-align: center;
}
}

View File

@ -0,0 +1,34 @@
import { useRouter } from "next/router";
import React from "react";
import classes from "./classes.module.scss";
import DefaultLegalDashboard, { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Link from "next/link";
const pdfLinks: Record<ELegalOptions, string> = {
"mentions-legales": "https://s3.fr-par.scw.cloud/lecoffre.io-bucket/footer/mentions_legales.pdf",
"politique-de-confidentialite": "https://s3.fr-par.scw.cloud/lecoffre.io-bucket/footer/politique_confidentialite.pdf",
"politique-de-gestion-des-cookies": "https://s3.fr-par.scw.cloud/lecoffre.io-bucket/footer/politique_cookies.pdf",
cgs: "https://s3.fr-par.scw.cloud/lecoffre.io-bucket/footer/cgs.pdf",
cgu: "https://s3.fr-par.scw.cloud/lecoffre.io-bucket/footer/cgu.pdf",
};
export default function LegalInformations() {
const router = useRouter();
let { legalUid } = router.query;
const legalUidTyped = legalUid as ELegalOptions;
return (
<DefaultLegalDashboard>
<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"]}>
Votre navigateur ne prend pas en charge l'affichage des pdf&nbsp;
<Typography typo={ETypo.TEXT_LG_LIGHT} type="span">
<Link href={pdfLinks[legalUidTyped]} target="_blank" style={{ color: `var(${ETypoColor.COLOR_PRIMARY_500})` }}>
cliquez ici pour télécharger le pdf.
</Link>
</Typography>
</Typography>
</object>
</DefaultLegalDashboard>
);
}

View File

@ -0,0 +1,23 @@
import DefaultLegalDashboard, { ELegalOptions } from "@Front/Components/LayoutTemplates/DefaultLegalDashboard";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import { useEffect } from "react";
type IProps = {};
export default function Legal(props: IProps) {
const router = useRouter();
const { legalUid } = router.query;
useEffect(() => {
if (!legalUid && router.isReady) {
router.push(
Module.getInstance()
.get()
.modules.pages.Legal.pages.LegalInformations.props.path.replace("[legalUid]", ELegalOptions.LEGAL_MENTIONS),
);
}
}, [legalUid, router]);
return <DefaultLegalDashboard />;
}

View File

@ -117,7 +117,7 @@ export default function OfficeInformations(props: IProps) {
); );
return ( return (
<DefaultOfficeDashboard mobileBackText={"Liste des utilisateurs"}> <DefaultOfficeDashboard>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <div className={classes["header"]}>
<Typography typo={ETypo.TITLE_H1}>Office {officeSelected?.crpcen + " - " + officeSelected?.name}</Typography> <Typography typo={ETypo.TITLE_H1}>Office {officeSelected?.crpcen + " - " + officeSelected?.name}</Typography>

View File

@ -9,7 +9,7 @@ type IState = {};
export default class Offices extends BasePage<IProps, IState> { export default class Offices extends BasePage<IProps, IState> {
public override render(): JSX.Element { public override render(): JSX.Element {
return ( return (
<DefaultOfficeDashboard title={"Dossier"} mobileBackText={"Liste des offices"}> <DefaultOfficeDashboard title={"Dossier"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["no-folder-selected"]}> <div className={classes["no-folder-selected"]}>
<Typography typo={ETypo.TITLE_H1}>Informations des offices</Typography> <Typography typo={ETypo.TITLE_H1}>Informations des offices</Typography>

View File

@ -210,7 +210,7 @@ export default function UserInformations(props: IProps) {
}, [currentAppointment, getUser]); }, [currentAppointment, getUser]);
return ( return (
<DefaultUserDashboard mobileBackText={"Liste des utilisateurs"}> <DefaultUserDashboard>
{!isLoading && ( {!isLoading && (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <div className={classes["header"]}>

View File

@ -9,7 +9,7 @@ type IState = {};
export default class Users extends BasePage<IProps, IState> { export default class Users extends BasePage<IProps, IState> {
public override render(): JSX.Element { public override render(): JSX.Element {
return ( return (
<DefaultUserDashboard title={"Dossier"} mobileBackText={"Liste des utilisateurs"}> <DefaultUserDashboard title={"Dossier"}>
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["no-folder-selected"]}> <div className={classes["no-folder-selected"]}>
<Typography typo={ETypo.TITLE_H1}>Informations des utilisateurs</Typography> <Typography typo={ETypo.TITLE_H1}>Informations des utilisateurs</Typography>

View File

@ -390,6 +390,22 @@
} }
} }
} }
},
"Legal": {
"enabled": true,
"props": {
"path": "/legal",
"labelKey": "legal"
},
"pages": {
"LegalInformations": {
"enabled": true,
"props": {
"path": "/legal/[legalUid]",
"labelKey": "legal_informations"
}
}
}
} }
} }
} }

View File

@ -390,6 +390,22 @@
} }
} }
} }
},
"Legal": {
"enabled": true,
"props": {
"path": "/legal",
"labelKey": "legal"
},
"pages": {
"LegalInformations": {
"enabled": true,
"props": {
"path": "/legal/[legalUid]",
"labelKey": "legal_informations"
}
}
}
} }
} }
} }

View File

@ -390,6 +390,22 @@
} }
} }
} }
},
"Legal": {
"enabled": true,
"props": {
"path": "/legal",
"labelKey": "legal"
},
"pages": {
"LegalInformations": {
"enabled": true,
"props": {
"path": "/legal/[legalUid]",
"labelKey": "legal_informations"
}
}
}
} }
} }
} }

View File

@ -390,6 +390,22 @@
} }
} }
} }
},
"Legal": {
"enabled": true,
"props": {
"path": "/legal",
"labelKey": "legal"
},
"pages": {
"LegalInformations": {
"enabled": true,
"props": {
"path": "/legal/[legalUid]",
"labelKey": "legal_informations"
}
}
}
} }
} }
} }

View File

@ -0,0 +1,5 @@
import LegalInformations from "@Front/Components/Layouts/Legal/LegalInformations";
export default function Route() {
return <LegalInformations />;
}

View File

@ -0,0 +1,5 @@
import Legal from "@Front/Components/Layouts/Legal";
export default function Route() {
return <Legal />;
}