Skip to content

Commit

Permalink
feat: melhoria na UI de lista de itens para evitar duplicação (SOS-RS…
Browse files Browse the repository at this point in the history
…#275)

## Related Issue

Closes SOS-RS#254 

## Overall

Este PR tem como objetivo alterar a forma como é feita a busca de itens
na tela de abrigo para evitar a duplicação de itens.

As alterações propostas incluem: 
- Remove o botão de cadastrar novo item no topo da página
- Adiciona autocomplete para busca de itens 
- Adiciona opção de cadastrar novo item no autocomplete

## Screen recording

https://github.com/SOS-RS/frontend/assets/8760873/74db3fa0-c45c-4b7a-afb2-d3d5279c0106
  • Loading branch information
vinny-silveira authored May 21, 2024
2 parents 286c867 + 97fb443 commit 02065f5
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 35 deletions.
94 changes: 60 additions & 34 deletions src/pages/EditShelterSupply/EditShelterSupply.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ChevronLeft, PlusCircle } from 'lucide-react';
import { ChevronLeft } from 'lucide-react';
import { useNavigate, useParams } from 'react-router-dom';
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';

import { DialogSelector, Header, LoadingScreen, TextField } from '@/components';
import { DialogSelector, Header, LoadingScreen } from '@/components';
import { Button } from '@/components/ui/button';
import { useShelter, useSupplies, useThrottle } from '@/hooks';
import { group, normalizedCompare } from '@/lib/utils';
import { SupplyRow } from './components';
import { SupplyRow, SupplySearch } from './components';
import { IDialogSelectorProps } from '@/components/DialogSelector/types';
import { ISupplyRowItemProps } from './components/SupplyRow/types';
import { ShelterSupplyServices } from '@/service';
Expand All @@ -25,32 +25,58 @@ const EditShelterSupply = () => {
const [filteredSupplies, setFilteredSupplies] = useState<IUseSuppliesData[]>(
[]
);
const [searchValue, setSearchValue] = useState<string>('');
const [searchedSupplies, setSearchedSupplies] = useState<IUseSuppliesData[]>(
[]
);
const shelterSupplyData = useMemo(() => {
return (shelter?.shelterSupplies ?? []).reduce(
(prev, current) => ({ ...prev, [current.supply.id]: current }),
{} as Record<string, IUseShelterDataSupply>
);
}, [shelter?.shelterSupplies]);

const [, setSearchSupplies] = useThrottle<string>(
{
throttle: 200,
callback: (value) => {
if (value) {
const filteredSupplies = supplies.filter((s) =>
normalizedCompare(s.name, value)
);
setSearchedSupplies(filteredSupplies);
} else {
setSearchedSupplies([]);
setSearch('');
}
},
},
[supplies]
);

const [, setSearch] = useThrottle<string>(
{
throttle: 400,
callback: (v) => {
if (v) {
setFilteredSupplies(
supplies.filter((s) => normalizedCompare(s.name, v))
callback: (value) => {
if (value) {
const filteredSupplies = supplies.filter((s) =>
normalizedCompare(s.name, value)
);
} else setFilteredSupplies(supplies);
setFilteredSupplies(filteredSupplies);
} else {
const storedSupplies = supplies.filter((s) => !!shelterSupplyData[s.id]);
setFilteredSupplies(storedSupplies);
}
},
},
[supplies]
[supplies, shelterSupplyData]
);
const [modalOpened, setModalOpened] = useState<boolean>(false);
const [loadingSave, setLoadingSave] = useState<boolean>(false);
const [modalData, setModalData] = useState<Pick<
IDialogSelectorProps,
'value' | 'onSave' | 'quantity'
> | null>();
const shelterSupplyData = useMemo(() => {
return (shelter?.shelterSupplies ?? []).reduce(
(prev, current) => ({ ...prev, [current.supply.id]: current }),
{} as Record<string, IUseShelterDataSupply>
);
}, [shelter?.shelterSupplies]);

const supplyGroups = useMemo(
() =>
group<IUseSuppliesData>(filteredSupplies ?? [], 'supplyCategory.name'),
Expand Down Expand Up @@ -112,8 +138,9 @@ const EditShelterSupply = () => {
);

useEffect(() => {
setFilteredSupplies(supplies);
}, [supplies]);
const storedSupplies = supplies.filter((s) => !!shelterSupplyData[s.id]);
setFilteredSupplies(storedSupplies);
}, [supplies, shelterSupplyData]);

if (loading) return <LoadingScreen />;

Expand Down Expand Up @@ -163,27 +190,26 @@ const EditShelterSupply = () => {
<div className="p-4 flex flex-col max-w-5xl w-full gap-3 items-start">
<h6 className="text-2xl font-semibold">Editar itens do abrigo</h6>
<p className="text-muted-foreground">
Para cada item da lista abaixo, informe a disponibilidade no abrigo
selecionado
Antes de adicionar um novo item, confira na busca abaixo se ele já não foi cadastrado.
</p>
<Button
variant="ghost"
className="flex gap-2 text-blue-500 [&_svg]:stroke-blue-500 font-medium text-lg hover:text-blue-600"
onClick={() => navigate(`/abrigo/${shelterId}/item/cadastrar`)}
>
<PlusCircle />
Cadastrar novo item
</Button>
<div className="w-full my-2">
<TextField
label="Buscar"
value={searchValue}
onChange={(ev) => {
setSearchValue(ev.target.value);
setSearch(ev.target.value);
<SupplySearch
supplyItems={searchedSupplies}
limit={5}
onSearch={(value) =>
setSearchSupplies(value)
}
onSelectItem={(item) => {
setSearch(item.name);
setSearchedSupplies([]);
}}
onAddNewItem={() => navigate(`/abrigo/${shelterId}/item/cadastrar`)}
/>
</div>

<p className="text-muted-foreground mt-3">
Para cada item da lista abaixo, informe a disponibilidade no abrigo selecionado.
</p>
<div className="flex flex-col gap-2 w-full my-4">
{Object.entries(supplyGroups).map(([key, values], idx) => {
const items: ISupplyRowItemProps[] = values
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Input } from '@/components/ui/input';
import { IUseSuppliesData } from '@/hooks/useSupplies/types';
import { Search, PlusCircle, X } from 'lucide-react';
import { useState } from 'react';
import { Fragment } from 'react/jsx-runtime';
import {ISupplySearchProps} from './types';

export const SupplySearch = ({
supplyItems,
limit = 10,
onSearch,
onSelectItem,
onAddNewItem
}: ISupplySearchProps) => {
const [searchValue, setSearchValue] = useState<string>('');
const [selectedItem, setSelectedItem] = useState<IUseSuppliesData | null>(null);

function onChangeInputHandler(event: React.ChangeEvent<HTMLInputElement>) {
setSearchValue(event.target.value);
onSearch(event.target.value);
}

function onSelectItemHandler(item: IUseSuppliesData) {
setSearchValue(item.name);
setSelectedItem(item);
onSelectItem(item);
}

function onAddNewItemHandler() {
setSelectedItem(null);
onAddNewItem();
}

function onClearClickHandler() {
setSelectedItem(null);
setSearchValue('');
onSearch('');
}

return (
<Fragment>
<div
className="flex items-center rounded-md border border-input px-3 h-10"
cmdk-input-wrapper=""
>
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<Input
type="text"
className="outline-none border-none focus-visible:ring-transparent h-8"
placeholder="Buscar itens..."
value={searchValue}
onChange={onChangeInputHandler}
/>
<X className="h-4 w-4 ml-2 hover:cursor-pointer" onClick={onClearClickHandler} />
</div>

{!!searchValue && !selectedItem ? (
<div className="flex-col items-center rounded-md border border-input p-3 bg-slate-50 mt-1">
{supplyItems.slice(0, limit).map((item) => (
<div
key={item.id}
className="h-10 flex items-center rounded-md p-2 hover:bg-slate-100 hover:cursor-pointer"
onClick={() => onSelectItemHandler(item)}
>
<span className="text-sm">{item.name}</span>
</div>
))}
<div
className="h-10 flex items-center rounded-md p-2 hover:bg-slate-100 hover:cursor-pointer"
onClick={onAddNewItemHandler}
>
<div className="flex gap-2 items-center">
<PlusCircle size={16} color="#0284c7" />
<span className="text-sm text-sky-600">Cadastrar novo item</span>
</div>
</div>
</div>
) : null}
</Fragment>
);
};
3 changes: 3 additions & 0 deletions src/pages/EditShelterSupply/components/SupplySearch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { SupplySearch } from './SupplySearch';

export { SupplySearch };
9 changes: 9 additions & 0 deletions src/pages/EditShelterSupply/components/SupplySearch/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IUseSuppliesData } from "@/hooks/useSupplies/types";

export interface ISupplySearchProps {
supplyItems: IUseSuppliesData[];
limit?: number;
onSearch: (value: string) => void;
onSelectItem: (item: IUseSuppliesData) => void;
onAddNewItem: () => void;
}
3 changes: 2 additions & 1 deletion src/pages/EditShelterSupply/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SupplyRow } from './SupplyRow';
import { SupplyRowInfo } from './SupplyRowInfo';
import { SupplySearch } from './SupplySearch';

export { SupplyRowInfo, SupplyRow };
export { SupplyRowInfo, SupplyRow, SupplySearch };

0 comments on commit 02065f5

Please sign in to comment.