Searchbar fully working

This commit is contained in:
Maxime Lalo 2024-07-24 12:39:18 +02:00
parent 9dedb60b69
commit 4960b51ee3
3 changed files with 189 additions and 173 deletions

View File

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

View File

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

View File

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