Front end Componentss (#5)

1) Progress Bar
https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=2c1d4ce11f4747b7bcd9814336a8e882

2) Folder Component
https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=1abbfeae008a486facf59e3cb2ccdd86

3) Api frontend (pas de tickets correspondants)

4) Ask/Pending/Validated/ documents (pas de tickets correspondants)

5) Multiselect [WIP] -> Error message not displaying when the user
search and didn't find in the corresponding options
https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=1d5ec1f29d2644d3ac7a0c309608c6e3

6) Document Notary Component
https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=e2da114abf5d40a5adc0c411f7f86c8d

7) Notary Documents For Users Components with filters for each users
according to the document status
https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=c682d55c2d42438f9a880493a5ac6b12

8) Fix Local Docker Compose Front (pas de tickets correspondants)

9) DropDown Component
https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=933bcc4418ea46f88c696e52b97d7bfc

10) Minor Fixes

https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=0c799226315c4d7ca7ee13648105cfcf

https://app.ora.pm/p/fb56ed95daa7456b888d266a050b9afa?v=86662&s=28564&t=k&c=eb9838e8ad894ac5b43e4c8562bc6450

---------

Co-authored-by: leabruchon <89223887+leabruchon@users.noreply.github.com>
This commit is contained in:
hugolxt 2023-04-04 16:44:43 +02:00 committed by GitHub
parent 8001cd4aa9
commit e98b8b601b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1784 additions and 4058 deletions

4167
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,13 +20,13 @@
"dotenv": "^16.0.3",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.18",
"next": "13.2.4",
"react": "18.2.0",
"react-apexcharts": "^1.4.0",
"react-dom": "18.2.0",
"react-tsparticles": "^2.9.3",
"react-select": "^5.7.2",
"reflect-metadata": "^0.1.13",
"sass": "^1.59.2",
"tsparticles": "^2.9.3",
"typedi": "^0.10.0",
"typescript": "4.9.5"
}

View File

@ -0,0 +1,5 @@
import BaseApiService from "@Front/Api/BaseApiService";
export default abstract class BaseNotary extends BaseApiService {
protected readonly namespaceUrl = this.backUrl.concat("/customers");
}

View File

@ -0,0 +1,51 @@
import { Service } from "typedi";
import BaseNotary from "../BaseCustomer";
import User from "le-coffre-resources/dist/Notary";
@Service()
export default class Users extends BaseNotary {
private static instance: Users;
private readonly baseURl = this.namespaceUrl.concat("/Users");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new Users();
} else {
return this.instance;
}
}
public async get(): Promise<User[]> {
const url = new URL(this.baseURl);
try {
return await this.getRequest<User[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getOne(uid: string): Promise<User> {
const url = new URL(this.baseURl.concat("/").concat(uid));
try {
return await this.getRequest<User>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
// public async post(params: User): Promise<User> {
// const url = new URL(this.baseURl);
// try {
// return await this.postRequest<User>(url, params);
// } catch (err) {
// this.onError(err);
// return Promise.reject(err);
// }
// }
}

View File

@ -0,0 +1,5 @@
import BaseApiService from "@Front/Api/BaseApiService";
export default abstract class BaseNotary extends BaseApiService {
protected readonly namespaceUrl = this.backUrl.concat("/notary");
}

View File

@ -0,0 +1,51 @@
import { Service } from "typedi";
import BaseNotary from "../BaseNotary";
import User from "le-coffre-resources/dist/Notary";
@Service()
export default class Users extends BaseNotary {
private static instance: Users;
private readonly baseURl = this.namespaceUrl.concat("/Users");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new Users();
} else {
return this.instance;
}
}
public async get(): Promise<User[]> {
const url = new URL(this.baseURl);
try {
return await this.getRequest<User[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getOne(uid: string): Promise<User> {
const url = new URL(this.baseURl.concat("/").concat(uid));
try {
return await this.getRequest<User>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
// public async post(params: User): Promise<User> {
// const url = new URL(this.baseURl);
// try {
// return await this.postRequest<User>(url, params);
// } catch (err) {
// this.onError(err);
// return Promise.reject(err);
// }
// }
}

View File

@ -1,55 +0,0 @@
import BaseApiService from "src/front/Api/BaseApiService";
import { IProject } from "src/front/interfaces";
import { Service } from "typedi";
type IPostProject = {
title: string;
network: string;
};
@Service()
export default class Project extends BaseApiService {
private static instance: Project;
private readonly baseURl = this.backUrl.concat("/projects");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new Project();
} else {
return this.instance;
}
}
public async getAllProject(): Promise<IProject[]> {
const url = new URL(this.baseURl);
try {
return await this.getRequest<IProject[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getOneProject(uuid: string): Promise<IProject> {
const url = new URL(this.baseURl.concat("/").concat(uuid));
try {
return await this.getRequest<IProject>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async postProject(params: IPostProject): Promise<IProject> {
const url = new URL(this.baseURl);
try {
return await this.postRequest<IProject>(url, params);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7.33366L8 9.33366L14.6667 2.66699" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 8V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V3.33333C2 2.97971 2.14048 2.64057 2.39052 2.39052C2.64057 2.14048 2.97971 2 3.33333 2H10.6667" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 598 B

View File

@ -0,0 +1,3 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 18L15.5 12L9.5 6" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 3.5003C17.2626 3.23766 17.5744 3.02932 17.9176 2.88718C18.2608 2.74503 18.6286 2.67188 19 2.67188C19.3714 2.67187 19.7392 2.74503 20.0824 2.88718C20.4256 3.02932 20.7374 3.23766 21 3.5003C21.2626 3.76295 21.471 4.07475 21.6131 4.41791C21.7553 4.76107 21.8284 5.12887 21.8284 5.5003C21.8284 5.87174 21.7553 6.23953 21.6131 6.58269C21.471 6.92585 21.2626 7.23766 21 7.5003L7.5 21.0003L2 22.5003L3.5 17.0003L17 3.5003Z" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 616 B

View File

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 5V19M4 12H18" stroke="#BD4B91" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

View File

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6H5H21" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 6V4C8 3.46957 8.21071 2.96086 8.58579 2.58579C8.96086 2.21071 9.46957 2 10 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4V6M19 6V20C19 20.5304 18.7893 21.0391 18.4142 21.4142C18.0391 21.7893 17.5304 22 17 22H7C6.46957 22 5.96086 21.7893 5.58579 21.4142C5.21071 21.0391 5 20.5304 5 20V6H19Z" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 11V17" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 11V17" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 830 B

View File

@ -0,0 +1,12 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3175_67304)">
<path d="M5.74016 1.33203H11.2602L15.1668 5.2387V10.7587L11.2602 14.6654H5.74016L1.8335 10.7587V5.2387L5.74016 1.33203Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 10.668H8.50667" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 5.33203V7.9987" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_3175_67304">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 702 B

View File

@ -98,4 +98,15 @@
&[touppercase="false"] {
text-transform: inherit;
}
&[variant="line"] {
color: $pink-flash;
background-color: transparent;
border-color: transparent;
padding: 0;
font-weight: 400;
font-size: 18px;
line-height: 22px;
text-decoration-line: underline;
}
}

View File

@ -18,7 +18,7 @@
}
input[type="checkbox"]::before {
content: url("../../../Assets/icons/check.svg");
content: url("../../../Assets/Icons/check.svg");
place-content: center;
display: grid;
width: 16px;

View File

@ -0,0 +1,25 @@
@import "@Themes/constants.scss";
.root {
border: 1px solid $grey-medium;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
&.DEPOSITED{
border-color: $orange-soft;
}
&.VALIDATED{
border-color: $green-soft;
}
.valid-radius{
background-color: $green-flash;
padding: 6px;
border-radius: 20px;
}
.trash{
cursor: pointer;
}
}

View File

@ -0,0 +1,54 @@
import React from "react";
import classes from "./classes.module.scss";
import { Document } from "le-coffre-resources/dist/Customer";
import Typography, { ITypo } from "../../Typography";
import Image from "next/image";
import TrashIcon from "@Assets/Icons/trash.svg";
import ValidIcon from "@Assets/Icons/check-valid.svg";
import classNames from "classnames";
import WarningBadge from "../../WarningBadge";
type IProps = {
document: {
uid: Document["uid"];
document_type: Document["document_type"];
document_status: Document["document_status"];
}
openDeletionModal?: (uid: Document["uid"]) => void;
};
type IState = {};
export default class DocumentNotary extends React.Component<IProps, IState> {
public constructor(props: IProps) {
super(props);
this.onOpenDeletionModal = this.onOpenDeletionModal.bind(this);
}
public override render(): JSX.Element {
return <div className={classNames(classes["root"], classes[this.props.document.document_status])}>
<div>
<Typography typo={ITypo.P_SB_16}>{this.props.document?.document_type?.name}</Typography>
<Typography typo={ITypo.CAPTION_14}>Aucun document déposé</Typography>
</div>
{this.renderIcon()}
</div>;
}
private renderIcon(): JSX.Element {
switch (this.props.document.document_status) {
case "VALIDATED":
return <div className={classes["valid-radius"]}>
<Image src={ValidIcon} alt="valid icon" />
</div>
case "PENDING":
return <WarningBadge />
default:
return <Image src={TrashIcon} alt="trash icon" className={classes["trash"]} onClick={this.onOpenDeletionModal}/>;
}
}
private onOpenDeletionModal(): void {
if(!this.props.openDeletionModal) return;
this.props.openDeletionModal(this.props.document.uid);
}
}

View File

@ -0,0 +1,23 @@
@import "@Themes/constants.scss";
.root {
display: inline-flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 24px;
border: 1px solid $grey-medium;
.left-side {
display: inline-flex;
justify-content: space-between;
align-items: center;
.warning-circle {
margin-left: 32px;
padding: 6px;
border-radius: 100px;
background-color: $orange-flash;
}
}
}

View File

@ -0,0 +1,33 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ITypo } from "../Typography";
import Image from "next/image";
import ChevronIcon from "@Assets/Icons/chevron.svg";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import WarningBadge from "../WarningBadge";
type IProps = {
folder: {
folder_number: OfficeFolder["folder_number"];
documents?: OfficeFolder["documents"];
};
}
type IState = {};
export default class FolderContainer extends React.Component<IProps, IState> {
public override render(): JSX.Element {
return <div className={classes["root"]}>
<div className={classes["left-side"]}>
<Typography typo={ITypo.P_16}>{"Dossier ".concat(this.props.folder.folder_number)}</Typography>
{this.countPendingDocuments() > 0 && <WarningBadge />}
</div>
<Image alt="chevron" src={ChevronIcon} />
</div>;
}
private countPendingDocuments(): number {
if(!this.props.folder.documents) return 0;
return this.props.folder.documents?.filter((document) => document.document_status === "PENDING").length ?? 0;
}
}

View File

@ -1,8 +1,8 @@
import React from "react";
import classes from "./classes.module.scss";
import Image from "next/image";
import BurgerIcon from "@Assets/icons/burger.svg";
import CrossIcon from "@Assets/icons/cross.svg";
import BurgerIcon from "@Assets/Icons/burger.svg";
import CrossIcon from "@Assets/Icons/cross.svg";
import BurgerModal from "./BurgerModal";
type IProps = {

View File

@ -1,7 +1,7 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import CloseIcon from "@Assets/icons/cross.svg";
import CloseIcon from "@Assets/Icons/cross.svg";
import Image from "next/image";
import ToastHandler from "@Front/Components/DesignSystem/Toasts/ToastsHandler";
import Toasts, { IToast } from "@Front/Stores/Toasts";

View File

@ -1,10 +1,10 @@
import React from "react";
import classes from "./classes.module.scss";
import Image from "next/image";
import NotificationIcon from "@Assets/icons/notification.svg";
import NotificationIcon from "@Assets/Icons/notification.svg";
import Toasts, { IToast } from "@Front/Stores/Toasts";
import NotificationModal from "./NotificationModal";
import InfoIcon from "@Assets/icons/info.svg";
import InfoIcon from "@Assets/Icons/info.svg";
type IProps = {
isModalOpen: boolean;

View File

@ -1,7 +1,7 @@
import React from "react";
import classes from "./classes.module.scss";
import Image from "next/image";
import ProfileIcon from "@Assets/icons/user.svg";
import ProfileIcon from "@Assets/Icons/user.svg";
import ProfileModal from "./ProfileModal";
type IProps = {

View File

@ -1,6 +1,7 @@
import React from "react";
import LoaderIcon from "assets/images/icons/loader.svg";
import LoaderIcon from "assets/images/Icons/loader.svg";
import classes from "./classes.module.scss";
import Image from "next/image";
interface IProps {
className?: string;
@ -8,7 +9,7 @@ interface IProps {
export default class Loader extends React.Component<IProps> {
public override render(): JSX.Element {
return <img src={LoaderIcon} className={[classes["loader"], this.props.className].filter(Boolean).join(" ")} />;
return <Image src={LoaderIcon} className={[classes["loader"], this.props.className].filter(Boolean).join(" ")} alt={"Loader"} />;
// <LoaderIcon className={[classes["loader"], this.props.className].filter(Boolean).join(" ")} />;
}
}

View File

@ -1,6 +1,6 @@
import React from "react";
import Image from "next/image";
import DisconnectIcon from "@Assets/icons/disconnect.svg";
import DisconnectIcon from "@Assets/Icons/disconnect.svg";
import classes from "./classes.module.scss";
import Typography, { ITypo } from "../Typography";

View File

@ -0,0 +1,49 @@
@import "@Themes/constants.scss";
.root {
.label-container {
cursor: pointer;
padding: 24px;
display: flex;
flex-direction: column;
flex: 1;
position: relative;
border: 1px solid $grey-medium;
background-color: transparent;
&.active{
padding: 16px 24px;
}
.label {
font-family: var(--font-primary);
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 18px;
text-transform: uppercase;
color: var(--color-primary-8);
}
.input-container {
display: flex;
outline: none;
gap: 16px;
border: none;
width: 100%;
>div {
width: 100%;
}
}
.is-active-placeholder {
position: absolute;
top: -11px;
left: 8px;
background-color: #ffffff;
z-index: 1;
padding: 0 16px;
}
}
}

View File

@ -0,0 +1,79 @@
import React from "react";
import ReactSelect, { ActionMeta, MultiValue, Options, PropsValue } from "react-select";
import { styles } from "./styles";
import classes from "./classes.module.scss";
import { IOption } from "../../Select";
import Typography, { ITypo } from "../../Typography";
import classNames from "classnames";
type IProps = {
options: IOption[];
label?: string | JSX.Element;
placeholder?: string;
onChange?: (newValue: MultiValue<IOption>, actionMeta: ActionMeta<IOption>) => void;
defaultValue?: PropsValue<IOption>;
value?: PropsValue<IOption>;
isMulti?: boolean;
shouldCloseMenuOnSelect?: boolean;
isOptionDisabled?: (option: IOption, selectValue: Options<IOption>) => boolean;
};
type IState = {
isSelectedOption: boolean;
};
export default class MultiSelect extends React.Component<IProps, IState> {
public static defaultProps: Partial<IProps> = {
placeholder: "Sélectionner une option...",
}
constructor(props: IProps) {
super(props);
this.state = {
isSelectedOption: this.props.defaultValue ? true : false
}
this.onChange = this.onChange.bind(this);
this.onEmptyResearch = this.onEmptyResearch.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<div className={classNames(classes["label-container"], this.state.isSelectedOption && classes["active"])}>
{this.props.label && <div className={classes["label"]}>{this.props.label}</div>}
{this.state.isSelectedOption &&
<>
< Typography typo={ITypo.NAV_INPUT_16}>
<div className={classes["is-active-placeholder"]}>{this.props.placeholder}</div>
</Typography>
</>
}
<div className={classes["input-container"]}>
<ReactSelect
placeholder={this.props.placeholder}
options={this.props.options}
styles={styles}
onChange={this.onChange}
value={this.props.value}
defaultValue={this.props.defaultValue}
closeMenuOnSelect={this.props.shouldCloseMenuOnSelect}
isMulti
isOptionDisabled={this.props.isOptionDisabled}
noOptionsMessage={this.onEmptyResearch}
/>
</div>
</div>
</div >
);
}
private onChange(newValue: MultiValue<IOption>, actionMeta: ActionMeta<IOption>) {
this.props.onChange && this.props.onChange(newValue, actionMeta);
this.setState({ isSelectedOption: newValue.length > 0 });
}
private onEmptyResearch(){
return "Aucune option correspondante"
}
}

View File

@ -0,0 +1,96 @@
export const styles = {
option: (provided: any, props: { isSelected: boolean; isFocused: boolean }) => ({
...provided,
cursor: "pointer",
padding: "8px 24px",
fontFamily: "var(--font-primary)",
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-primary)",
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",
}),
MultiValueGeneric: (provided: any) => ({
...provided,
color: "red",
}),
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",
}
}),
indicatorsContainer: (provided: any) => ({
...provided,
display: "none",
}),
listBox: (provided: any) => ({
...provided,
color: "red",
fontSize: "16px",
}),
};

View File

@ -21,7 +21,6 @@
}
.root {
--animation-delay: 1ms;
position: fixed;
z-index: 6;
top: 0;
@ -31,6 +30,7 @@
display: flex;
align-items: center;
justify-content: center;
--animation-delay: 1ms;
animation: smooth-appear var(--animation-delay) $custom-easing;
&[data-will-close="true"] {
@ -48,7 +48,7 @@
.container {
position: relative;
width: 594px;
width: 610px;
max-height: 90%;
background: $white;
box-shadow: 0px 6px 12px rgba(255, 255, 255, 0.11);

View File

@ -5,7 +5,7 @@ import Header from "./Elements/Header";
import Loader from "./Elements/Loader";
import Typography, { ITypo } from "../Typography";
import CrossIcon from "@Assets/icons/cross.svg";
import CrossIcon from "@Assets/Icons/cross.svg";
import Image from "next/image";
export type IProps = {

View File

@ -0,0 +1,26 @@
@import "@Themes/constants.scss";
.root {
position: relative;
background-color: $grey-medium;
box-shadow: inset 0px 4px 8px rgba(0, 0, 0, 0.08);
height: 12px;
border-radius: 5px;
}
.progress {
position: relative;
border-radius: 5px;
display: block;
width: 100%;
height: 100%;
transition: width 300ms;
background-color: $turquoise-flash;
.percentage{
width: 37px;
position: absolute;
top: 16px;
left: 50%;
transform: translate(-50%);
}
}

View File

@ -0,0 +1,22 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ITypo } from "../../Typography";
type IProps = {
percentage: number;
};
export default class ProgressBar extends React.Component<IProps> {
public override render(): JSX.Element {
const quantity = (this.props.percentage.toFixed(2)).toString().concat("%")
return (
<div className={classes["root"]}>
<div className={classes["progress"]} style={{ width: quantity }} >
<Typography typo={ITypo.P_16}>
<div className={classes["percentage"]}>{quantity}</div>
</Typography>
</div>
</div>
);
}
}

View File

@ -0,0 +1,13 @@
@import "@Themes/constants.scss";
.root {
font-weight: 500;
font-size: 14px;
line-height: 20px;
font-feature-settings: "salt" on;
height: 70px;
.title {
margin-bottom: 8px;
}
}

View File

@ -0,0 +1,26 @@
import React from "react";
import ProgressBar from "./ProgressBar";
import classes from "./classes.module.scss";
import Typography, { ITypo } from "../Typography";
type IProps = {
currentNumber: number;
total: number;
title: string;
};
export default class QuantityProgressBar extends React.Component<IProps> {
public override render(): JSX.Element {
let numerator: number = this.props.currentNumber;
if (this.props.currentNumber > this.props.total) { numerator = this.props.total }
const percentage = (numerator / this.props.total) * 100;
return (
<div className={classes["root"]}>
<div className={classes["title"]}>
<Typography typo={ITypo.P_16}>{this.props.title}</Typography>
</div>
<ProgressBar percentage={percentage} />
</div>
);
}
}

View File

@ -0,0 +1,131 @@
@import "@Themes/constants.scss";
.root {
display: flex;
position: relative;
flex-direction: column;
width: 100%;
border: 1px solid $grey-medium;
.container-label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: $white;
cursor: pointer;
padding: 24px;
z-index: 2;
&[data-border-right-collapsed="true"] {
border-radius: 8px 0 0 8px;
}
.container-input {
display: flex;
align-items: center;
span {
display: flex;
.icon {
display: flex;
margin-right: 8px;
align-items: center;
}
}
.placeholder {
position: absolute;
top: 24px;
left: 8px;
background-color: $white;
padding: 0 16px;
&[data-open="true"] {
transform: translateY(-36px);
}
}
}
.chevron-icon {
height: 24px;
fill: $grey;
transition: all 350ms $custom-easing;
&[data-open="true"] {
transform: rotate(90deg);
}
}
}
.container-ul {
padding-left: 24px;
z-index: 3;
list-style: none;
margin: 0;
outline: 0;
display: flex;
flex-direction: column;
width: 100%;
transition: height 350ms $custom-easing, opacity 350ms $custom-easing;
opacity: 1;
overflow: hidden;
top: 50px;
background-color: $white;
&[data-open="false"] {
height: 0;
opacity: 0;
border: none;
}
}
.container-li {
display: flex;
justify-content: flex-start;
align-items: center;
padding-bottom: 24px;
border-radius: 8px;
cursor: pointer;
background: var(--color-neutral-50);
&:hover {
background: var(--color-neutral-100);
}
&:active {
background: var(--color-neutral-200);
}
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.token-icon {
max-width: 20px;
display: flex;
align-items: center;
margin-right: 11px;
>svg {
height: 20px;
margin-right: 11px;
}
>img {
height: 20px;
width: 20px;
}
}
.backdrop {
position: fixed;
z-index: 1;
inset: 0;
}
}

View File

@ -0,0 +1,144 @@
import classNames from "classnames";
import React, { FormEvent, ReactNode } from "react";
import ChevronIcon from "@Assets/Icons/chevron.svg";
import Typography, { ITypo } from "../Typography";
import classes from "./classes.module.scss";
import WindowStore from "@Front/Stores/Window";
import Image from "next/image";
type IProps = {
selectedOption?: IOption;
onChange: (selectedOption: IOption) => void;
options: IOption[];
hasBorderRightCollapsed?: boolean;
placeholder?: string;
className?: string;
};
export type IOption = {
value: unknown;
label: string;
icon?: ReactNode;
};
type IState = {
isOpen: boolean;
listWidth: number;
listHeight: number;
};
export default class Select extends React.Component<IProps, IState> {
private contentRef = React.createRef<HTMLUListElement>();
private rootRef = React.createRef<HTMLDivElement>();
private removeOnresize = () => { };
constructor(props: IProps) {
super(props);
this.state = {
isOpen: false,
listHeight: 0,
listWidth: 0,
};
this.toggle = this.toggle.bind(this);
this.onSelect = this.onSelect.bind(this);
}
public override render(): JSX.Element {
const selectedOption = this.props.selectedOption;
return (
<div className={classNames(classes["root"], this.props.className)} ref={this.rootRef}>
<label
className={classNames(classes["container-label"])}
data-open={this.state.isOpen}
onClick={this.toggle}
data-border-right-collapsed={this.props.hasBorderRightCollapsed}>
<div className={classNames(classes["container-input"])}>
<>
{selectedOption?.icon && (
<span className={classNames(classes["icon"], classes["token-icon"])}>{selectedOption?.icon}</span>
)}
<Typography typo={ITypo.P_18}>
<span className={classes["text"]}>{selectedOption?.label}</span>
</Typography>
<div className={classes["placeholder"]} data-open={selectedOption && true}>
<Typography typo={ITypo.NAV_INPUT_16}>
<span className={classes["text"]}>{this.props.placeholder ?? ""}</span>
</Typography>
</div>
</>
</div>
<Image className={classes["chevron-icon"]} data-open={this.state.isOpen} src={ChevronIcon} alt="chevron icon" />
</label>
<ul
className={classes["container-ul"]}
data-open={this.state.isOpen}
ref={this.contentRef}
style={{
height: this.state.listHeight + "px",
}}>
{this.props.options.map((option, index) => (
<li key={`${index}-${option.value}`} className={classes["container-li"]} onClick={(e) => this.onSelect(option, e)}>
<div className={classes["token-icon"]}>{option.icon}</div>
<Typography typo={ITypo.P_18}>
{option.label}
</Typography>
</li>
))}
</ul>
{this.state.isOpen && <div className={classes["backdrop"]} onClick={this.toggle} />}
</div>
);
}
static getDerivedStateFromProps(props: IProps, state: IState) {
if (props.selectedOption?.value) {
return {
value: props.selectedOption?.value,
};
}
return null;
}
public override componentDidMount(): void {
this.onResize();
this.removeOnresize = WindowStore.getInstance().onResize(() => this.onResize());
}
public override componentWillUnmount() {
this.removeOnresize();
}
private onResize() {
let listHeight = 0;
let listWidth = 0;
listWidth = this.rootRef.current?.scrollWidth ?? 0;
if (this.state.listHeight) {
listHeight = this.contentRef.current?.scrollHeight ?? 0;
}
this.setState({ listHeight, listWidth });
}
private toggle(e: FormEvent) {
e.preventDefault();
let listHeight = 0;
let listWidth = this.rootRef.current?.scrollWidth ?? 0;
if (!this.state.listHeight) {
listHeight = this.contentRef.current?.scrollHeight ?? 0;
}
this.setState((state) => {
return { isOpen: !state.isOpen, listHeight, listWidth };
});
}
private onSelect(option: IOption, e: React.MouseEvent<HTMLLIElement, MouseEvent>) {
this.props.onChange && this.props.onChange(option);
this.toggle(e);
}
}

View File

@ -1,6 +1,6 @@
import React from "react";
import Image from "next/image";
import ToolTipIcon from "@Assets/icons/tool-tip.svg";
import ToolTipIcon from "@Assets/Icons/tool-tip.svg";
import TooltipMUI, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
import styled from "@emotion/styled";

View File

@ -0,0 +1,13 @@
@import "@Themes/constants.scss";
.root {
width: 100%;
.title{
margin-bottom: 8px;
}
.content{
display: grid;
gap: 16px;
margin-top: 32px;
}
}

View File

@ -0,0 +1,34 @@
import React from "react";
import classes from "./classes.module.scss";
import { Document } from "le-coffre-resources/dist/Customer";
import Typography, { ITypo } from "../../Typography";
import DocumentNotary from "../../Document/DocumentNotary";
type IProps = {
title: string;
subtitle?: string;
documents: {
uid: Document["uid"];
document_type: Document["document_type"];
document_status: Document["document_status"];
}[] | null;
openDeletionModal: (uid: Document["uid"]) => void;
};
type IState = {};
export default class DocumentList extends React.Component<IProps, IState> {
public override render(): JSX.Element {
return <div className={classes["root"]}>
<div className={classes["title"]}>
<Typography typo={ITypo.P_SB_18}>{this.props.title}</Typography>
</div>
<Typography typo={ITypo.P_16}>{this.props.subtitle}</Typography>
<div className={classes["content"]}>
{this.props.documents && this.props.documents.map((document) => {
return <DocumentNotary document={document} key={document.uid} openDeletionModal={this.props.openDeletionModal}/>
})}
</div>
</div>;
}
}

View File

@ -0,0 +1,28 @@
@import "@Themes/constants.scss";
.root {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px;
background-color: $grey-soft;
width: 100%;
.content {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
@media (max-width: $screen-ls) {
grid-template-columns: 1fr 1fr;
gap: 32px;
}
}
.icons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
}

View File

@ -0,0 +1,59 @@
import React from "react";
import classes from "./classes.module.scss";
import { Contact } from "le-coffre-resources/dist/Notary";
import Typography, { ITypo } from "../../Typography";
import Image from "next/image";
import PenIcon from "@Assets/Icons/pen.svg";
import WarningBadge from "../../WarningBadge";
type IProps = {
contact: {
first_name: Contact["first_name"],
last_name: Contact["last_name"],
phone_number: Contact["phone_number"],
cell_phone_number: Contact["cell_phone_number"],
email: Contact["email"],
}
};
type IState = {};
export default class UserFolderHeader extends React.Component<IProps, IState> {
public override render(): JSX.Element {
return <div className={classes["root"]}>
<div className={classes["content"]}>
<div className={classes["container"]}>
<Typography typo={ITypo.NAV_INPUT_16}>Nom</Typography>
<Typography typo={ITypo.P_18}>{this.props.contact.last_name}</Typography>
</div>
<div className={classes["container"]}>
<Typography typo={ITypo.NAV_INPUT_16}>Prénom</Typography>
<Typography typo={ITypo.P_18}>{this.props.contact.first_name}</Typography>
</div>
<div className={classes["container"]}>
<Typography typo={ITypo.NAV_INPUT_16}>Numéro de téléphone</Typography>
<Typography typo={ITypo.P_18}>{this.formatPhoneNumber(this.props.contact.phone_number) ?? this.formatPhoneNumber(this.props.contact.cell_phone_number)}</Typography>
</div>
<div className={classes["container"]}>
<Typography typo={ITypo.NAV_INPUT_16}>E-mail</Typography>
<Typography typo={ITypo.P_18}>{this.props.contact.email}</Typography>
</div>
</div>
<div className={classes["icons"]}>
<WarningBadge />
<Image src={PenIcon} alt="edit" className={classes["edit-icon"]} />
</div>
</div>;
}
private formatPhoneNumber(phoneNumber: string): string {
if (!phoneNumber) return "";
const output = phoneNumber.split('').map((char, index) => {
if(index%2) return char + " ";
return char;
});
return output.join("");
}
}

View File

@ -0,0 +1,66 @@
@import "@Themes/constants.scss";
@keyframes smooth-appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes smooth-disappear {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.root {
border: 1px solid $grey;
padding: 16px;
transition: all 350ms $custom-easing;
.header {
display: flex;
align-items: center;
justify-content: space-between;
.chevron-icon {
margin-left: 16px;
transform: rotate(90deg);
transition: all 350ms $custom-easing;
cursor: pointer;
&.open {
transform: rotate(-90deg);
}
}
}
.container {
margin-top: 32px;
--animation-delay: 1ms;
animation: smooth-appear var(--animation-delay) $custom-easing;
&[data-will-close="true"] {
animation: smooth-disappear var(--animation-delay) $custom-easing;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 64px;
margin-top: 32px;
}
.button-container {
display: inline-grid;
justify-items: start;
gap: 32px;
}
}
}

View File

@ -0,0 +1,146 @@
import React from "react";
import classes from "./classes.module.scss";
import UserFolderHeader from "./UserFolderHeader";
import Customer, { Document } from "le-coffre-resources/dist/Customer";
import Image from "next/image";
import ChevronIcon from "@Assets/Icons/chevron.svg";
import QuantityProgressBar from "../QuantityProgressBar";
import classNames from "classnames";
import DocumentList from "./DocumentList";
import Button, { EButtonVariant } from "../Button";
import PlusIcon from "@Assets/Icons/plus.svg"
import Confirm from "../Modal/Confirm";
type IProps = {
customer: Customer
animationDelay?: number;
};
type IState = {
isOpen: boolean;
isOpenDeletionModal: boolean;
willClose: boolean;
};
export default class UserFolder extends React.Component<IProps, IState> {
static defaultProps = {
animationDelay: 300
};
public rootRefElement = React.createRef<HTMLDivElement>();
constructor(props: IProps) {
super(props);
this.state = {
isOpen: false,
isOpenDeletionModal: false,
willClose: false
};
this.toggleOpen = this.toggleOpen.bind(this);
this.closeDeletionModal = this.closeDeletionModal.bind(this);
this.openDeletionModal = this.openDeletionModal.bind(this);
this.openComponent = this.openComponent.bind(this);
this.closeComponent = this.closeComponent.bind(this);
}
public override render(): JSX.Element {
const documentsAsked: Document[] | null = this.getDocumentsByStatus("ASKED");
const otherDocuments: Document[] | null = this.getOtherDocuments(documentsAsked);
return <div className={classes["root"]}>
<Confirm
isOpen={this.state.isOpenDeletionModal}
onClose={this.closeDeletionModal}
closeBtn
header={"Supprimer la demande de document ?"}
cancelText={"Cancel"}
confirmText={"Confirmer"}>
Êtes-vous vous de vouloir supprimer la demande de document ?
</Confirm>
<div className={classes["header"]}>
<UserFolderHeader contact={this.props.
customer.contact} />
<Image src={ChevronIcon} alt="chevron open close" className={classNames(classes["chevron-icon"], this.state.isOpen && classes["open"])} onClick={this.toggleOpen} />
</div>
{this.state.isOpen &&
<div className={classes["container"]} data-will-close={this.state.willClose.toString()} ref={this.rootRefElement}>
<QuantityProgressBar title="Complétion du dossier" total={100} currentNumber={this.calculateDocumentsPercentageProgress()} />
<div className={classes["content"]}>
<DocumentList documents={documentsAsked} title="Documents demandés" openDeletionModal={this.openDeletionModal} />
<DocumentList documents={otherDocuments} title="Documents à valider / validés" subtitle="Vous avez des documents à valider." openDeletionModal={this.openDeletionModal} />
</div>
<div className={classes["button-container"]}>
<Button variant={EButtonVariant.LINE} icon={<Image src={PlusIcon} alt="plus icon" />}>Demander un autre document </Button>
<Button>Envoyer un mail de demande de documents</Button>
</div>
</div>
}
</div>;
}
public override componentDidUpdate(): void {
this.rootRefElement.current?.style.setProperty("--animation-delay", this.props.animationDelay!.toString().concat("ms"));
}
private calculateDocumentsPercentageProgress(): number {
if (!this.props.customer.documents) return 0;
const totalDocuments: number = this.props.customer.documents.length;
const numberDocumentsAsked: number = this.getDocumentsByStatus("ASKED")?.length || 0;
return Math.round((numberDocumentsAsked / totalDocuments) * 100);
}
private getDocumentsByStatus(status: string): Document[] | null {
if (!this.props.customer.documents) return null;
return this.props.customer.documents.filter((document) => document.document_status === status);
}
private getOtherDocuments(documentToExclude: Document[] | null): Document[] | null {
if (!this.props.customer.documents) return null;
if (!documentToExclude) return this.props.customer.documents;
return this.props.customer.documents.filter((document) => !documentToExclude.includes(document));
}
private toggleOpen(): void {
if (this.state.isOpen) {
this.closeComponent();
}
else {
this.openComponent();
}
}
private openComponent(): void {
this.setState({
isOpen: true
});
}
private closeComponent(): void {
if (this.state.willClose) return;
this.setState({ willClose: true })
window.setTimeout(() => {
this.setState({
isOpen: false,
willClose: false
});
}, this.props.animationDelay)
}
private openDeletionModal(uid: string): void {
console.log("Delete item > ", uid);
// TODO: call API to delete document
this.setState({
isOpenDeletionModal: true
});
}
private closeDeletionModal(): void {
this.setState({
isOpenDeletionModal: false
})
}
}

View File

@ -0,0 +1,7 @@
@import "@Themes/constants.scss";
.root {
background-color: $orange-flash;
padding: 6px;
border-radius: 20px;
}

View File

@ -0,0 +1,15 @@
import React from "react";
import classes from "./classes.module.scss";
import Image from "next/image";
import WarningIcon from "@Assets/Icons/warning.svg";
type IProps = {};
type IState = {};
export default class WarningBadge extends React.Component<IProps, IState> {
public override render(): JSX.Element {
return <div className={classes["root"]}>
<Image src={WarningIcon} alt="warning icon" />
</div>;
}
}

View File

@ -2,8 +2,8 @@ import React, { RefObject } from "react";
import classes from "./classes.module.scss";
import classNames from "classnames";
import Image from "next/image";
import ErrorIcon from "@Assets/icons/input-error.svg";
import SuccessIcon from "@Assets/icons/input-success.svg";
import ErrorIcon from "@Assets/Icons/input-error.svg";
import SuccessIcon from "@Assets/Icons/input-success.svg";
type IProps = {
inputRef?: RefObject<HTMLInputElement>;

View File

@ -11,4 +11,8 @@
.inline-flex {
display: inline-flex;
}
.folder-conatainer{
width: 389px ;
}
}

View File

@ -0,0 +1,155 @@
import {
Address,
Office,
DeedType,
Deed,
OfficeFolder,
Contact,
Customer,
DocumentType,
Document,
} from "le-coffre-resources/dist/Notary";
import { ECustomerStatus } from "le-coffre-resources/dist/Customer/Customer";
import { EFolderStatus } from "le-coffre-resources/dist/Customer/OfficeFolder";
export const address: Address = {
uid: "a&23",
address: "123",
city: "France",
zip_code: 78140,
created_at: new Date(),
updated_at: new Date(),
};
export const office: Office = {
uid: "111213",
idNot: "12EE12",
name: "Office 1",
crpcen: "AZezdz",
address: address,
created_at: new Date(),
updated_at: new Date(),
office_status: "ACTIVATED",
};
export const deedType: DeedType = {
uid: "123312",
name: "Acte mariage",
description: "dzsdaf",
archived_at: new Date(),
office: office,
created_at: new Date(),
updated_at: new Date(),
};
export const deed: Deed = {
uid: "123124",
deed_type: deedType,
created_at: new Date(),
updated_at: new Date(),
};
export const folder: OfficeFolder = {
uid: "11123312",
folder_number: "12331",
name: "Mon dossier",
status: EFolderStatus.ARCHIVED,
deed: deed,
office: office,
created_at: new Date(),
updated_at: new Date(),
description: "Description",
archived_description: "Archived description",
};
export const contact: Contact = {
uid: "123123",
first_name: "John",
last_name: "Doe",
email: "johnDoe@gmail.com",
address: address,
created_at: new Date(),
updated_at: new Date(),
cell_phone_number: "0132249865",
phone_number: "0132249865",
civility: "MALE",
};
export const docType: DocumentType = {
name: "Acte de naissance",
uid: "123123",
created_at: new Date(),
updated_at: new Date(),
public_description: "Acte de naissance public description",
private_description: "Acte de naissance private description",
archived_at: new Date(),
};
export const customer: Customer = {
uid: "123123",
contact: contact,
created_at: new Date(),
updated_at: new Date(),
status: ECustomerStatus.VALIDATED,
};
export const document: Document = {
uid: "0",
depositor: customer,
document_status: "ASKED",
folder: folder,
document_type: docType,
updated_at: new Date(),
created_at: new Date(),
};
export const document2: Document = {
uid: "1",
depositor: customer,
document_status: "ASKED",
folder: folder,
document_type: docType,
updated_at: new Date(),
created_at: new Date(),
};
export const documentPending: Document = {
uid: "2",
depositor: customer,
document_status: "PENDING",
folder: folder,
document_type: docType,
updated_at: new Date(),
created_at: new Date(),
};
export const documentDeposited: Document = {
uid: "3",
depositor: customer,
document_status: "VALIDATED",
folder: folder,
document_type: docType,
updated_at: new Date(),
created_at: new Date(),
};
export const customer2: Customer = {
uid: "123123",
contact: contact,
created_at: new Date(),
updated_at: new Date(),
status: ECustomerStatus.VALIDATED,
documents: [document, document2, documentPending, documentDeposited],
};
export const folderWithPendingDocument: OfficeFolder = {
uid: "11123312",
folder_number: "12332",
name: "Mon dossier",
status: EFolderStatus.ARCHIVED,
deed: deed,
office: office,
created_at: new Date(),
updated_at: new Date(),
description: "Description",
archived_description: "Archived description",
documents: [documentPending],
};

View File

@ -10,13 +10,23 @@ import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import ToolTip from "@Front/Components/DesignSystem/ToolTip";
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import InputField from "@Front/Components/DesignSystem/Form/Elements/InputField";
import QuantityProgressBar from "@Front/Components/DesignSystem/QuantityProgressBar";
import "reflect-metadata";
import FolderContainer from "@Front/Components/DesignSystem/FolderContainer";
import { customer2, document, documentDeposited, documentPending, folder, folderWithPendingDocument } from "./dummyData"
import DocumentNotary from "@Front/Components/DesignSystem/Document/DocumentNotary";
import Select, { IOption } from "@Front/Components/DesignSystem/Select";
import UserFolder from "@Front/Components/DesignSystem/UserFolder";
import MultiSelect from "@Front/Components/DesignSystem/Materials/MultiSelect";
type IState = {
isModalDisplayed: boolean;
selectedOption?: IOption;
};
type IProps = {};
export default class DesignSystem extends BasePage<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
@ -24,12 +34,23 @@ export default class DesignSystem extends BasePage<IProps, IState> {
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.onSelectedOption = this.onSelectedOption.bind(this);
}
public override render(): JSX.Element {
const selectOptions: IOption[] = [
{ value: "1", label: "Divorce" },
{ value: "2", label: "Succession" },
{ value: "3", label: "Vente immobilière" },
]
return (
<DefaultTemplate title={"HomePage"}>
<div className={classes["root"]}>
<div className={classes["section"]}>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.H1}>Website design System</Typography>
@ -44,6 +65,7 @@ export default class DesignSystem extends BasePage<IProps, IState> {
</Typography>
</div>
</div>
<MultiSelect options={selectOptions} placeholder="Numéro CRPCEN"/>
<div className={classes["section"]}>
<div className={classes["sub-section"]}>
@ -127,6 +149,86 @@ export default class DesignSystem extends BasePage<IProps, IState> {
<InputField name="input field" fakeplaceholder="number place hodler" type="number" />
</div>
</div>
<div className={classes["section"]}>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.H3}>Progress bar component</Typography>
</div>
<div className={classes["sub-section"]}>
<QuantityProgressBar currentNumber={10} total={100} title={"Complétion du dossier client"} />
</div>
<div className={classes["sub-section"]}>
<QuantityProgressBar currentNumber={30} total={100} title={"Complétion du dossier client"} />
</div>
<div className={classes["sub-section"]}>
<QuantityProgressBar currentNumber={70} total={100} title={"Complétion du dossier client"} />
</div>
</div>
<div className={classes["section"]}>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.H3}>Folder container component</Typography>
</div>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.P_16}>Folder with no document to validate</Typography>
<div className={classes["folder-conatainer"]}>
<FolderContainer folder={folder} />
</div>
</div>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.P_16}>Folder with document waiting for being validate</Typography>
<div className={classes["folder-conatainer"]}>
<FolderContainer folder={folderWithPendingDocument} />
</div>
</div>
</div>
<div className={classes["section"]}>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.H3}>Select component</Typography>
</div>
<div className={classes["sub-section"]}>
<div className={classes["folder-conatainer"]}>
<Select options={selectOptions} onChange={this.onSelectedOption} placeholder={"Type dacte"} selectedOption={this.state.selectedOption} />
</div>
</div>
</div>
<div className={classes["section"]}>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.H3}>Notary Documents</Typography>
</div>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.P_16}>Documents ASKED</Typography>
<div className={classes["folder-conatainer"]}>
<DocumentNotary document={document} />
</div>
</div>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.P_16}>Documents PENDING</Typography>
<div className={classes["folder-conatainer"]}>
<DocumentNotary document={documentPending} />
</div>
</div>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.P_16}>Documents VALIDATED</Typography>
<div className={classes["folder-conatainer"]}>
<DocumentNotary document={documentDeposited} />
</div>
</div>
</div>
<div className={classes["section"]}>
<div className={classes["sub-section"]}>
<Typography typo={ITypo.H3}>Notary Documents</Typography>
</div>
<div className={classes["sub-section"]}>
<UserFolder customer={customer2} />
</div>
</div>
</div>
</DefaultTemplate>
);
@ -148,4 +250,10 @@ export default class DesignSystem extends BasePage<IProps, IState> {
};
Toasts.getInstance().open(toast);
}
private onSelectedOption(option: IOption) {
this.setState({
selectedOption: option,
});
}
}

View File

@ -0,0 +1,67 @@
import EventEmitter from "events";
export default class WindowStore {
private static ctx: WindowStore;
private readonly event = new EventEmitter();
private constructor() {
WindowStore.ctx = this;
this.iniEvents();
}
public static getInstance() {
if (!WindowStore.ctx) return new this();
return WindowStore.ctx;
}
public onScrollYDirectionChange(callback: (scrollYDifference: number) => void) {
this.event.on("scrollYDirectionChange", callback);
return () => {
this.event.off("scrollYDirectionChange", callback);
};
}
public onResize(callback: (window: Window) => void) {
this.event.on("resize", callback);
return () => {
this.event.off("resize", callback);
};
}
public onClick(callback: (e: MouseEvent) => void) {
this.event.on("click", callback);
return () => {
this.event.off("click", callback);
};
}
private iniEvents(): void {
window.addEventListener("scroll", (e: Event) => this.scrollYHandler());
window.addEventListener("resize", (e: Event) => this.resizeHandler());
document.addEventListener("click", (e: MouseEvent) => this.clickHandler(e), true);
}
private clickHandler(e: MouseEvent) {
this.event.emit("click", e);
}
private scrollYHandler = (() => {
let previousY: number = window.scrollY;
let snapShotY: number = previousY;
let previousYDirection: number = 1;
return (): void => {
const scrollYDirection = window.scrollY - previousY > 0 ? 1 : -1;
if (previousYDirection !== scrollYDirection) {
snapShotY = window.scrollY;
}
this.event.emit("scrollYDirectionChange", snapShotY - window.scrollY);
previousY = window.scrollY;
previousYDirection = scrollYDirection;
};
})();
private resizeHandler() {
this.event.emit("resize", window);
}
}

View File

@ -1,3 +1,3 @@
{
"version": "v0.0.1"
"version": "v0.1.0"
}