Merged dev
This commit is contained in:
commit
b71b919f95
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
.label {
|
||||||
|
padding: 0px var(--spacing-2, 16px);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -30,6 +30,7 @@
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
.content {
|
.content {
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
|
overflow: auto;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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} />;
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
@import "@Themes/constants.scss";
|
|
||||||
|
|
||||||
.root {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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"]} />
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
.hidden-input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.errors-container {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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",
|
|
||||||
}),
|
|
||||||
};
|
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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%;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 />;
|
||||||
|
}
|
@ -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%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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"]}>
|
||||||
|
@ -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>
|
||||||
|
@ -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%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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} />
|
||||||
|
@ -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>
|
||||||
|
@ -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"]}>
|
||||||
|
@ -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} />
|
||||||
|
@ -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>
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
.pdf-viewer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
|
||||||
|
.no-pdf-text {
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
23
src/front/Components/Layouts/Legal/index.tsx
Normal file
23
src/front/Components/Layouts/Legal/index.tsx
Normal 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 />;
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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"]}>
|
||||||
|
@ -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>
|
||||||
|
@ -390,6 +390,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Legal": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal",
|
||||||
|
"labelKey": "legal"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"LegalInformations": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal/[legalUid]",
|
||||||
|
"labelKey": "legal_informations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,6 +390,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Legal": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal",
|
||||||
|
"labelKey": "legal"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"LegalInformations": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal/[legalUid]",
|
||||||
|
"labelKey": "legal_informations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,6 +390,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Legal": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal",
|
||||||
|
"labelKey": "legal"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"LegalInformations": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal/[legalUid]",
|
||||||
|
"labelKey": "legal_informations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,6 +390,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Legal": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal",
|
||||||
|
"labelKey": "legal"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"LegalInformations": {
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"path": "/legal/[legalUid]",
|
||||||
|
"labelKey": "legal_informations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
src/pages/legal/[legalUid]/index.tsx
Normal file
5
src/pages/legal/[legalUid]/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import LegalInformations from "@Front/Components/Layouts/Legal/LegalInformations";
|
||||||
|
|
||||||
|
export default function Route() {
|
||||||
|
return <LegalInformations />;
|
||||||
|
}
|
5
src/pages/legal/index.tsx
Normal file
5
src/pages/legal/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import Legal from "@Front/Components/Layouts/Legal";
|
||||||
|
|
||||||
|
export default function Route() {
|
||||||
|
return <Legal />;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user