From be7507e9435a17e3762a667692959ac8762f3da2 Mon Sep 17 00:00:00 2001 From: kevindsousa Date: Fri, 17 May 2024 20:12:06 -0300 Subject: [PATCH 01/37] fix: :bug: Ajuste do tamanho do botao reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixado tamanho do botão para corrigir erro no Header --- src/components/Header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 9dde1265..00e1529e 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -28,7 +28,7 @@ const Header = React.forwardRef((props, ref) => { {title} -
+
{endAdornment}
From 0b176a60eb06a1f00233559e2e771084ccd12e4a Mon Sep 17 00:00:00 2001 From: Tiago Date: Sun, 19 May 2024 17:02:37 -0300 Subject: [PATCH 02/37] =?UTF-8?q?feat:=20Corre=C3=A7=C3=A3o=20do=20t=C3=AD?= =?UTF-8?q?tulo=20com/sem=20filtros=20#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close: #247 --- .../Home/components/ShelterListView/ShelterListView.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Home/components/ShelterListView/ShelterListView.tsx b/src/pages/Home/components/ShelterListView/ShelterListView.tsx index ec57fc7b..35e83f69 100644 --- a/src/pages/Home/components/ShelterListView/ShelterListView.tsx +++ b/src/pages/Home/components/ShelterListView/ShelterListView.tsx @@ -36,7 +36,10 @@ const ShelterListView = React.forwardRef( return (

- Abrigos disponíveis ({count}) + {searchParams.toString() + ? `Abrigos encontrados (${count})` + : 'Total de abrigos' + }

Date: Mon, 20 May 2024 14:31:48 -0300 Subject: [PATCH 03/37] Criado botao --- src/App.tsx | 2 ++ src/components/BackToTop/BackToTop.tsx | 35 ++++++++++++++++++++++++++ src/components/BackToTop/index.ts | 3 +++ 3 files changed, 40 insertions(+) create mode 100644 src/components/BackToTop/BackToTop.tsx create mode 100644 src/components/BackToTop/index.ts diff --git a/src/App.tsx b/src/App.tsx index 478da36d..f0dd1a25 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { BrowserRouter } from 'react-router-dom'; import { Routes } from './routes/Routes'; import { SessionProvider } from './contexts'; import { Toaster } from './components/ui/toaster'; +import { BackToTop } from '@/components/BackToTop'; const App = () => { return ( @@ -11,6 +12,7 @@ const App = () => { + diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx new file mode 100644 index 00000000..5cb3054c --- /dev/null +++ b/src/components/BackToTop/BackToTop.tsx @@ -0,0 +1,35 @@ +import { useState } from "react" + +const BackToTop =() => { + + const [isVisible, setVisibility] = useState(true) + + const scrollToTop = () => { + window.scrollTo(0,0) + + console.log("click!") + } + + const toggleVisibility = () => { + if (window.scrollY >= window.innerHeight) { + setVisibility(true) + } else { + setVisibility(false) + } + + console.log("scrooled") + } + + +return (isVisible && ( + +)); +} + +export { BackToTop }; \ No newline at end of file diff --git a/src/components/BackToTop/index.ts b/src/components/BackToTop/index.ts new file mode 100644 index 00000000..2e570724 --- /dev/null +++ b/src/components/BackToTop/index.ts @@ -0,0 +1,3 @@ +import { BackToTop } from "./BackToTop"; + +export { BackToTop }; From 458788d28171bd56d1a44e7fff99fdad373eeee4 Mon Sep 17 00:00:00 2001 From: leonardo Date: Mon, 20 May 2024 15:11:17 -0300 Subject: [PATCH 04/37] Adicionado funcoes para o botao. COMENTADO OVERFLOW-X:HIDDEN no global.css --- src/components/BackToTop/BackToTop.tsx | 21 ++++++++------------- src/global.css | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx index 5cb3054c..8c73990d 100644 --- a/src/components/BackToTop/BackToTop.tsx +++ b/src/components/BackToTop/BackToTop.tsx @@ -2,32 +2,27 @@ import { useState } from "react" const BackToTop =() => { - const [isVisible, setVisibility] = useState(true) + const [isVisible, setVisibility] = useState(false) const scrollToTop = () => { - window.scrollTo(0,0) + window.scrollTo({top:0, behavior:"smooth"}) - console.log("click!") + console.log(window.scrollY) } - const toggleVisibility = () => { - if (window.scrollY >= window.innerHeight) { + window.addEventListener("scroll", () => { + if ( window.innerHeight / 2 < window.scrollY) { setVisibility(true) } else { setVisibility(false) } - - console.log("scrooled") - } + }) return (isVisible && ( )); } diff --git a/src/global.css b/src/global.css index 80c42b80..5046f9d0 100644 --- a/src/global.css +++ b/src/global.css @@ -88,6 +88,6 @@ #root { max-width: 100vw; - overflow-x: hidden; + /*overflow-x: hidden;*/ } } From d81fb7ee442af698e234b50e5a09d1ba5d027477 Mon Sep 17 00:00:00 2001 From: leonardo Date: Mon, 20 May 2024 15:20:04 -0300 Subject: [PATCH 05/37] removendo necessidade de remover overflow-x:hidden no global.css --- src/components/BackToTop/BackToTop.tsx | 1 + src/global.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx index 8c73990d..aff261f1 100644 --- a/src/components/BackToTop/BackToTop.tsx +++ b/src/components/BackToTop/BackToTop.tsx @@ -17,6 +17,7 @@ const BackToTop =() => { setVisibility(false) } }) + //document.getElementById('root').addEventListener('scroll', (e) => console.log(e.target.scrollTop)) return (isVisible && ( diff --git a/src/global.css b/src/global.css index 5046f9d0..80c42b80 100644 --- a/src/global.css +++ b/src/global.css @@ -88,6 +88,6 @@ #root { max-width: 100vw; - /*overflow-x: hidden;*/ + overflow-x: hidden; } } From 509703ce22aa028af2761fc2e10c521a2a13f9e9 Mon Sep 17 00:00:00 2001 From: leonardo Date: Mon, 20 May 2024 15:29:17 -0300 Subject: [PATCH 06/37] resolvendo funcoes --- src/components/BackToTop/BackToTop.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx index aff261f1..0b86eab8 100644 --- a/src/components/BackToTop/BackToTop.tsx +++ b/src/components/BackToTop/BackToTop.tsx @@ -2,22 +2,26 @@ import { useState } from "react" const BackToTop =() => { - const [isVisible, setVisibility] = useState(false) + const [isVisible, setVisibility] = useState(!false) const scrollToTop = () => { - window.scrollTo({top:0, behavior:"smooth"}) + let root = document.getElementById('root') + if (!root) {return} - console.log(window.scrollY) + root.scrollTo({top:0, behavior:"smooth"}) + } - window.addEventListener("scroll", () => { - if ( window.innerHeight / 2 < window.scrollY) { + document.getElementById("root")?.addEventListener('scroll', (e) => { + let CurrentScrollHeight = e.target.scrollTop + let WindowHeight = window.innerHeight + + if ( CurrentScrollHeight > WindowHeight / 2) { setVisibility(true) } else { setVisibility(false) } }) - //document.getElementById('root').addEventListener('scroll', (e) => console.log(e.target.scrollTop)) return (isVisible && ( From ba31363b6056a116c42e951c47d003b359af1024 Mon Sep 17 00:00:00 2001 From: Leonardo <57924586+Lmedeiros-leiman@users.noreply.github.com> Date: Mon, 20 May 2024 17:30:26 -0300 Subject: [PATCH 07/37] Finalizando. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit durante meus testes esqueci de inverter o estado inicial do botão. --- src/components/BackToTop/BackToTop.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx index 0b86eab8..283831db 100644 --- a/src/components/BackToTop/BackToTop.tsx +++ b/src/components/BackToTop/BackToTop.tsx @@ -2,7 +2,7 @@ import { useState } from "react" const BackToTop =() => { - const [isVisible, setVisibility] = useState(!false) + const [isVisible, setVisibility] = useState(false) const scrollToTop = () => { let root = document.getElementById('root') @@ -32,4 +32,4 @@ return (isVisible && ( )); } -export { BackToTop }; \ No newline at end of file +export { BackToTop }; From ba6a7c0b537cec8cc7d0f8e6106a922f57f283cb Mon Sep 17 00:00:00 2001 From: leonardo Date: Mon, 20 May 2024 23:13:55 -0300 Subject: [PATCH 08/37] aplicado o design recomendado pela barbiebrega --- src/components/BackToTop/BackToTop.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx index 283831db..c27eca46 100644 --- a/src/components/BackToTop/BackToTop.tsx +++ b/src/components/BackToTop/BackToTop.tsx @@ -1,4 +1,6 @@ import { useState } from "react" +import { ArrowUp } from "lucide-react" + const BackToTop =() => { @@ -13,6 +15,7 @@ const BackToTop =() => { } document.getElementById("root")?.addEventListener('scroll', (e) => { + if (e.target === null) {return} let CurrentScrollHeight = e.target.scrollTop let WindowHeight = window.innerHeight @@ -26,9 +29,12 @@ const BackToTop =() => { return (isVisible && ( + > )); } From 9a30b5c4448f3651818d74ab5997b53c4ba66d08 Mon Sep 17 00:00:00 2001 From: Tiago Date: Wed, 22 May 2024 08:00:00 -0300 Subject: [PATCH 09/37] =?UTF-8?q?feat:=20Corre=C3=A7=C3=A3o=20do=20t=C3=AD?= =?UTF-8?q?tulo=20com/sem=20filtros=20#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close: #247 --- src/pages/Home/components/ShelterListView/ShelterListView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Home/components/ShelterListView/ShelterListView.tsx b/src/pages/Home/components/ShelterListView/ShelterListView.tsx index 35e83f69..98c9eb2f 100644 --- a/src/pages/Home/components/ShelterListView/ShelterListView.tsx +++ b/src/pages/Home/components/ShelterListView/ShelterListView.tsx @@ -38,7 +38,7 @@ const ShelterListView = React.forwardRef(

{searchParams.toString() ? `Abrigos encontrados (${count})` - : 'Total de abrigos' + : `Total de abrigos (${count})` }

Date: Thu, 23 May 2024 21:31:46 -0300 Subject: [PATCH 10/37] Removido erro de tipagem. --- src/components/BackToTop/BackToTop.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx index c27eca46..bc64c207 100644 --- a/src/components/BackToTop/BackToTop.tsx +++ b/src/components/BackToTop/BackToTop.tsx @@ -16,7 +16,7 @@ const BackToTop =() => { document.getElementById("root")?.addEventListener('scroll', (e) => { if (e.target === null) {return} - let CurrentScrollHeight = e.target.scrollTop + let CurrentScrollHeight = (e.target as HTMLElement).scrollTop let WindowHeight = window.innerHeight if ( CurrentScrollHeight > WindowHeight / 2) { From afde60652488b61fd43b96f4b48de23efffac805 Mon Sep 17 00:00:00 2001 From: Eric Ricielle Date: Sat, 25 May 2024 00:30:06 -0300 Subject: [PATCH 11/37] =?UTF-8?q?#287=20-=20[FIX]=20Itens=20Cadastrados=20?= =?UTF-8?q?sem=20Categoria=20est=C3=A3o=20indo=20para=20Medicamentos=20(#2?= =?UTF-8?q?96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #287 * Delete src/components/Icon directory * Update SupplyRowInfo.tsx * RollBack SupplyRowInfo.tsx * Update SupplyRow.tsx * Update EditShelterSupply.tsx * Update CreateSupply.tsx - De forma a evitar termos genéricos demais, é solicitado ao usuário que registre um recurso com no mínimo 3 caracteres. Validação via Yup. * Update CreateSupply.tsx - Bloqueia cadastro de items com números e caracteres especiais. Validação via Yup. * Update CreateSupply.tsx * Update CreateSupply.tsx - Limite de 30 itens retornados enquanto o usuário está digitando o termo desejado. * Update CreateSupply.tsx - Bloqueia caracteres especiais; - Requer no mínimo 3 letras (bloqueia apenas números). --- src/pages/CreateSupply/CreateSupply.tsx | 319 ++++++++++++------ src/pages/CreateSupply/components/index.ts | 3 + src/pages/CreateSupply/components/modal.tsx | 191 +++++++++++ src/pages/CreateSupply/components/types.ts | 35 ++ .../components/SupplyRow/SupplyRow.tsx | 10 +- src/service/supply/types.ts | 2 +- 6 files changed, 446 insertions(+), 114 deletions(-) create mode 100644 src/pages/CreateSupply/components/index.ts create mode 100644 src/pages/CreateSupply/components/modal.tsx create mode 100644 src/pages/CreateSupply/components/types.ts diff --git a/src/pages/CreateSupply/CreateSupply.tsx b/src/pages/CreateSupply/CreateSupply.tsx index 18fd1a3a..78f8025c 100644 --- a/src/pages/CreateSupply/CreateSupply.tsx +++ b/src/pages/CreateSupply/CreateSupply.tsx @@ -6,7 +6,7 @@ import * as Yup from 'yup'; import { CircleStatus, Header, LoadingScreen, TextField } from '@/components'; import { Button } from '@/components/ui/button'; import { useToast } from '@/components/ui/use-toast'; -import { useSupplyCategories } from '@/hooks'; +import { useShelter, useSupplies, useSupplyCategories } from '@/hooks'; import { Select, SelectContent, @@ -19,12 +19,21 @@ import { getSupplyPriorityProps } from '@/lib/utils'; import { ShelterSupplyServices, SupplyServices } from '@/service'; import { ICreateShelterSupply } from '@/service/shelterSupply/types'; import { clearCache } from '@/api/cache'; +import { Fragment } from 'react/jsx-runtime'; +import { IUseSuppliesData } from '@/hooks/useSupplies/types'; +import { useState } from 'react'; +import { ModalCreateSupply } from './components'; const CreateSupply = () => { const navigate = useNavigate(); const { shelterId = '-1' } = useParams(); + const { data: shelter } = useShelter(shelterId); const { toast } = useToast(); const { data: supplyCategories, loading } = useSupplyCategories(); + const { data: supplies } = useSupplies(); + + const [modalOpened, setModalOpened] = useState(false); + const [supplyId, setSupplyId] = useState(''); const { errors, @@ -36,9 +45,8 @@ const CreateSupply = () => { } = useFormik>({ initialValues: { name: '', - supplyCategoryId: supplyCategories?.at(0)?.id ?? '-1', shelterId, - priority: SupplyPriority.NotNeeded, + priority: SupplyPriority.Needing, }, enableReinitialize: true, validateOnBlur: false, @@ -46,8 +54,18 @@ const CreateSupply = () => { validateOnMount: false, validationSchema: Yup.object().shape({ shelterId: Yup.string().required('Este campo deve ser preenchido'), - name: Yup.string().required('Este campo deve ser preenchido'), - quantity: Yup.number().typeError('Insira um valor númerico').moreThan(0, 'O valor tem que ser maior do que 0').optional(), + name: Yup.string() + .matches(/^[a-zA-ZÀ-ÿ0-9\s]*$/, "O nome não deve conter caracteres especiais") + .test('min-letters', 'O nome deve conter pelo menos 3 letras', value => { + const letterCount = (value?.match(/[a-zA-ZÀ-ÿ]/g) || []).length; + return letterCount >= 3; + }) + .min(3, 'Insira no mínimo 3 caracteres') + .required('Este campo deve ser preenchido'), + quantity: Yup.number() + .typeError('Insira um valor númerico') + .moreThan(0, 'O valor tem que ser maior do que 0') + .optional(), priority: Yup.string().required('Este campo deve ser preenchido'), supplyCategoryId: Yup.string().required('Este campo deve ser preenchido'), }), @@ -78,122 +96,201 @@ const CreateSupply = () => { }, }); + const filteredSupplies = + values.name.length > 2 + ? supplies.filter((e) => { + const normalizedSearchTerm = values.name + .trim() + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''); + return e.name + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .includes(normalizedSearchTerm); + }) + : []; + + const renderSupplies = (element: IUseSuppliesData, key: number) => { + if (values.name.length < 3 || key > 30) return <>; + + return ( + +
{ + setSupplyId(element.id); + setModalOpened(true); + }} + > + {element.name} +
+
+ ); + }; + if (loading) return ; return ( -
-
navigate(-1)} - > - - - } - /> -
-
-
Cadastrar novo item
-

- Informe o nome do item que você deseja cadastrar, a categoria e a - prioridade -

-
- -
- - setFieldValue('supplyCategoryId', v)} + > + + - {category.name} - - ))} - - -
- -
- - + ))} + + +
+ {errors.supplyCategoryId} +
+
+ +
+ + +
+
{errors.priority}
-
-
- -
- +
+ +
+ +
- + ); }; diff --git a/src/pages/CreateSupply/components/index.ts b/src/pages/CreateSupply/components/index.ts new file mode 100644 index 00000000..87db6c65 --- /dev/null +++ b/src/pages/CreateSupply/components/index.ts @@ -0,0 +1,3 @@ +import { ModalCreateSupply } from './modal'; + +export { ModalCreateSupply }; diff --git a/src/pages/CreateSupply/components/modal.tsx b/src/pages/CreateSupply/components/modal.tsx new file mode 100644 index 00000000..ce4a838e --- /dev/null +++ b/src/pages/CreateSupply/components/modal.tsx @@ -0,0 +1,191 @@ +import { useState, useEffect } from 'react'; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Label } from '@/components/ui/label'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { ShelterSupplyServices } from '@/service'; +import { Link } from 'react-router-dom'; +import { IMessage, IProps, ISupplies } from './types'; + +const ModalCreateSupply = (props: IProps) => { + const { + open, + onClose, + title, + description = '', + options, + supplies, + supplyId, + shelterId, + } = props; + const [data, setData] = useState({ + quantity: 0, + priority: '10', + }); + const [message, setMessage] = useState({ + error: false, + register: false, + successSub: false, + message: '', + }); + + useEffect(() => { + supplies.forEach((element: ISupplies) => { + if (element.supply.id === supplyId) { + setMessage((prev) => ({ + ...prev, + register: true, + message: 'Verificamos que você já tem esse item registrado.', + })); + setData({ + quantity: element.quantity, + priority: element.priority.toString(), + }); + } + }); + }, [supplies, supplyId]); + + const onSave = () => { + if (!message.register) { + ShelterSupplyServices.create({ + shelterId, + supplyId, + priority: parseInt(data.priority), + quantity: data.quantity, + }) + .then(() => { + setMessage((prev) => ({ + ...prev, + successSub: true, + message: 'Registro salvo com sucesso!', + })); + }) + .catch(() => + alert( + 'Ocorreu um erro. Por favor, tente novamente ou entre em contato com o suporte.' + ) + ); + } else { + ShelterSupplyServices.update(shelterId, supplyId, { + priority: parseInt(data.priority), + quantity: data.quantity, + }) + .then(() => { + setMessage((prev) => ({ + ...prev, + successSub: true, + message: 'Registro salvo com sucesso!', + })); + }) + .catch(() => + alert( + 'Ocorreu um erro. Por favor, tente novamente ou entre em contato com o suporte.' + ) + ); + } + }; + + return ( + + + {message.successSub ? ( + <> +
+ {message.message} +
+ + + + + + + ) : ( + <> + + + {title} + + {description && ( + {description} + )} + + {message.message && ( +
+ {message.message} +
+ )} +
+ +
+ + setData((prev) => ({ + ...prev, + quantity: parseInt(event.target.value), + })) + } + placeholder="Quantidade" + min={0} + /> +
+
+
+ + setData((prev: any) => ({ + ...prev, + priority: v, + })) + } + > + {options.map((option: any, idx: any) => ( +
+ + +
+ ))} +
+
+ + + + + )} +
+
+ ); +}; + +export { ModalCreateSupply }; diff --git a/src/pages/CreateSupply/components/types.ts b/src/pages/CreateSupply/components/types.ts new file mode 100644 index 00000000..27af8ed0 --- /dev/null +++ b/src/pages/CreateSupply/components/types.ts @@ -0,0 +1,35 @@ +import { IDialogSelectorItemProps } from '@/components/DialogSelector/types'; + +export interface IProps { + open: boolean; + onClose?: () => void; + title: string; + description?: string; + options: IDialogSelectorItemProps[]; + supplies: [ + { + supply: { + id: string; + }; + quantity: number; + priority: string; + } + ]; + supplyId: string; + shelterId: string; +} + +export interface ISupplies { + supply: { + id: string; + }; + quantity: number; + priority: string; +} + +export interface IMessage { + error: boolean; + message: string; + register: boolean; + successSub: boolean; +} diff --git a/src/pages/EditShelterSupply/components/SupplyRow/SupplyRow.tsx b/src/pages/EditShelterSupply/components/SupplyRow/SupplyRow.tsx index a4d59c7d..9aba6cd4 100644 --- a/src/pages/EditShelterSupply/components/SupplyRow/SupplyRow.tsx +++ b/src/pages/EditShelterSupply/components/SupplyRow/SupplyRow.tsx @@ -1,13 +1,19 @@ import { SupplyPriority } from '@/service/supply/types'; import { SupplyRowInfo } from '../SupplyRowInfo'; import { ISupplyRowProps } from './types'; +import { Archive } from 'lucide-react'; const SupplyRow = (props: ISupplyRowProps) => { const { name, items, onClick } = props; return ( -
-

{name}

+
+
+

+ + {name.toUpperCase()} +

+
{items.map((item, idy) => ( Date: Sun, 26 May 2024 04:04:45 -0300 Subject: [PATCH 12/37] Update - Melhoria na listagem de suplementos (#249) --- src/components/Chip/Chip.tsx | 37 ++++++++++++------- src/components/Chip/types.ts | 2 +- .../ShelterSupplyCategoryRow.tsx | 10 ++++- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/components/Chip/Chip.tsx b/src/components/Chip/Chip.tsx index 6440a5c3..af3e3777 100644 --- a/src/components/Chip/Chip.tsx +++ b/src/components/Chip/Chip.tsx @@ -5,23 +5,32 @@ import { cva } from 'class-variance-authority'; const Chip = React.forwardRef((props, ref) => { const { label, className, variant = 'info', ...rest } = props; - const variants = cva('px-4 py-1.5 font-medium text-sm md:text-md rounded-2xl', { - variants: { - variant: { - warn: 'bg-light-yellow', - success: 'bg-light-green', - danger: 'bg-light-red', - alert: 'bg-light-orange', - info: 'bg-light-blue', + const variants = cva( + 'px-4 py-1.5 font-medium text-sm md:text-md rounded-2xl', + { + variants: { + variant: { + warn: 'bg-light-yellow', + success: 'bg-light-green', + danger: 'bg-light-red', + alert: 'bg-light-orange', + info: 'bg-light-blue', + moreInfo: 'bg-gray-200 text-black-600', + }, }, - }, - defaultVariants: { - variant: 'info', - }, - }); + defaultVariants: { + variant: 'info', + }, + } + ); return ( - + {label} ); diff --git a/src/components/Chip/types.ts b/src/components/Chip/types.ts index 22bfb972..f660a809 100644 --- a/src/components/Chip/types.ts +++ b/src/components/Chip/types.ts @@ -1,4 +1,4 @@ -export type ChipVariant = 'info' | 'success' | 'warn' | 'danger'; +export type ChipVariant = 'info' | 'success' | 'warn' | 'danger' | 'moreInfo'; export interface IChipProps extends React.ComponentPropsWithoutRef<'div'> { label: string; diff --git a/src/pages/Home/components/ShelterSupplyCategoryRow/ShelterSupplyCategoryRow.tsx b/src/pages/Home/components/ShelterSupplyCategoryRow/ShelterSupplyCategoryRow.tsx index a711f3f0..f8f3aa79 100644 --- a/src/pages/Home/components/ShelterSupplyCategoryRow/ShelterSupplyCategoryRow.tsx +++ b/src/pages/Home/components/ShelterSupplyCategoryRow/ShelterSupplyCategoryRow.tsx @@ -13,6 +13,10 @@ const ShelterSupplyCategoryRow = React.forwardRef< if (tags.length === 0) return ; + const moreInfoLabel = () => { + return `+${tags.length - 10} ${tags.length > 11 ? 'itens' : 'item'}`; + }; + return (
@@ -21,9 +25,13 @@ const ShelterSupplyCategoryRow = React.forwardRef<

{description &&

{description}

}
- {tags.map((s, idx) => ( + {tags.slice(0, 10).map((s, idx) => ( ))} + + {tags.length > 10 && ( + + )}
); From aeb8822ad68101d2cd7542d7e59543ae0b197210 Mon Sep 17 00:00:00 2001 From: Diego Dario Date: Mon, 27 May 2024 18:03:56 -0300 Subject: [PATCH 13/37] feat: add multi option for priority queryParam --- src/pages/Home/Home.tsx | 28 +++++++++++++---- src/pages/Home/components/Filter/Filter.tsx | 34 ++++++++------------- src/pages/Home/components/Filter/types.ts | 6 ++-- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 4f51f924..d1105a9f 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -7,11 +7,11 @@ import { BurgerMenu, Footer, Header } from '@/components'; import { useShelters, useThrottle } from '@/hooks'; import { Button } from '@/components/ui/button'; import { Filter, ShelterListView } from './components'; -import { IFilterFormProps } from './components/Filter/types'; +import { IFilterFormProps, IFilterSubmittionForm } from './components/Filter/types'; const initialFilterData: IFilterFormProps = { search: '', - priority: null, + priority: [], supplyCategoryIds: [], supplyIds: [], shelterStatus: [], @@ -54,21 +54,37 @@ const Home = () => { [shelters.page, shelters.perPage, shelters.count] ); + const factorySearchArgs = useCallback((values: IFilterFormProps) => { + const searchQueryArgs: IFilterSubmittionForm = { + search: values.search, + priority: values.priority.join(), + supplyCategoryIds: values.supplyCategoryIds, + supplyIds: values.supplyIds, + shelterStatus: values.shelterStatus, + cities: values.cities + } + + return searchQueryArgs; + }, []) + const onSubmitFilterForm = useCallback( (values: IFilterFormProps) => { setOpenModal(false); setFilterData(values); - const searchQuery = qs.stringify(values, { + + const searchQuery = qs.stringify(factorySearchArgs(values), { skipNulls: true, }); + setSearchParams(searchQuery); + refresh({ params: { search: searchQuery, }, }); }, - [refresh, setSearchParams] + [refresh, setSearchParams, factorySearchArgs] ); const handleFetchMore = useCallback(() => { @@ -76,7 +92,7 @@ const Home = () => { ...shelters.filters, page: shelters.page + 1, perPage: shelters.perPage, - search: qs.stringify(filterData), + search: qs.stringify(factorySearchArgs(filterData)), }; refresh( @@ -85,7 +101,7 @@ const Home = () => { }, true ); - }, [refresh, filterData, shelters.filters, shelters.page, shelters.perPage]); + }, [refresh, filterData, shelters.filters, shelters.page, shelters.perPage, factorySearchArgs]); return (
diff --git a/src/pages/Home/components/Filter/Filter.tsx b/src/pages/Home/components/Filter/Filter.tsx index 59caf9eb..f71d98b7 100644 --- a/src/pages/Home/components/Filter/Filter.tsx +++ b/src/pages/Home/components/Filter/Filter.tsx @@ -19,7 +19,6 @@ import { import { IFilterFormikProps, IFilterProps, - ISelectField, ShelterAvailabilityStatus, } from './types'; import { priorityOptions } from '@/lib/utils'; @@ -66,12 +65,10 @@ const Filter = (props: IFilterProps) => { { initialValues: { cities: data.cities ?? [], - priority: data.priority - ? { - label: priorityOpts[data.priority], - value: data.priority, - } - : null, + priority: data.priority.map((p: string) => ({ + label: priorityOpts[Number(p) as SupplyPriority], + value: p, + })), search: data.search, shelterStatus: data.shelterStatus.map((s) => ({ label: ShelterAvailabilityStatusMapped[s], @@ -103,7 +100,7 @@ const Filter = (props: IFilterProps) => { cities, } = values; onSubmit({ - priority: priority?.value ? +priority.value : null, + priority: priority.map((p) => p.value), search, shelterStatus: shelterStatus.map((s) => s.value), supplyCategoryIds: supplyCategories.map((s) => s.value), @@ -111,8 +108,7 @@ const Filter = (props: IFilterProps) => { cities, }); }, - } - ); + }); const supplyOptions = useMemo(() => { return supplies @@ -187,20 +183,14 @@ const Filter = (props: IFilterProps) => { ({ label, value: priority, - }), + }) )} - onChange={(v) => setFieldValue('priority', v)} + onChange={(v) => setFieldValue('priorities', v)} />
diff --git a/src/pages/Home/components/Filter/types.ts b/src/pages/Home/components/Filter/types.ts index 347633f8..3506e581 100644 --- a/src/pages/Home/components/Filter/types.ts +++ b/src/pages/Home/components/Filter/types.ts @@ -7,7 +7,7 @@ export interface ISelectField { export interface IFilterFormProps { search: string; - priority: string[]; + priorities: string[]; supplyCategoryIds: string[]; supplyIds: string[]; shelterStatus: ShelterAvailabilityStatus[]; @@ -16,7 +16,7 @@ export interface IFilterFormProps { export interface IFilterFormikProps { search: string; - priority: ISelectField[]; + priorities: ISelectField[]; supplyCategories: ISelectField[]; supplies: ISelectField[]; shelterStatus: ISelectField[]; From 8f523d0a8f48b77f3422b0254adf51eb73900354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Geraldo=20D=2E=20F?= Date: Thu, 30 May 2024 18:43:12 -0300 Subject: [PATCH 16/37] =?UTF-8?q?Feat:=20Carrinho=20de=20Doa=C3=A7=C3=B5es?= =?UTF-8?q?=20(#340)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 3 +- src/App.tsx | 10 +- src/components/BackToTop/BackToTop.tsx | 62 ++-- src/components/BurgerMenu/BurgerMenu.tsx | 13 +- src/components/DonationCart/DonationCart.tsx | 45 +++ .../DonationCartForm/DonationCartForm.tsx | 273 +++++++++++++++ .../components/DonationCartForm/index.ts | 3 + .../components/DonationCartForm/types.ts | 5 + .../DonationSuccess/DonationSuccess.tsx | 83 +++++ .../components/DonationSuccess/index.ts | 3 + .../components/DonationSuccess/types.ts | 4 + .../DonationCart/components/index.ts | 4 + src/components/DonationCart/index.ts | 3 + src/components/DonationCart/types.ts | 5 + .../DonationCartIcon/DonationCartIcon.tsx | 29 ++ src/components/DonationCartIcon/index.ts | 3 + src/components/DonationCartIcon/types.ts | 3 + src/components/Header/Header.tsx | 12 +- src/components/SearchInput/SearchInput.tsx | 19 +- src/components/SearchInput/types.ts | 7 +- src/components/TextField/TextField.tsx | 2 +- src/components/index.ts | 6 + src/components/ui/sheet.tsx | 75 ++--- .../DonationCartContext.tsx | 99 ++++++ src/contexts/DonationCartContext/index.ts | 6 + src/contexts/DonationCartContext/types.ts | 25 ++ src/contexts/index.ts | 13 +- src/hooks/index.ts | 2 + src/hooks/useDonationOrder/index.ts | 3 + src/hooks/useDonationOrder/types.ts | 57 ++++ .../useDonationOrder/useDonationOrder.tsx | 8 + src/hooks/useFetch/useFetch.tsx | 3 +- src/hooks/usePaginatedQuery/paths.ts | 1 + .../usePaginatedQuery/usePaginatedQuery.tsx | 14 +- src/hooks/useShelter/types.ts | 9 + src/hooks/useShelters/types.ts | 3 +- src/hooks/useShelters/useShelters.tsx | 3 +- src/lib/utils.ts | 11 +- src/pages/Home/components/Filter/Filter.tsx | 4 +- .../ShelterListView/ShelterListView.tsx | 6 +- src/pages/Shelter/Shelter.tsx | 310 ++++++++++-------- .../ShelterCategoryItems.tsx | 112 ------- .../components/ShelterCategoryItems/index.ts | 3 - .../components/ShelterCategoryItems/types.ts | 14 - .../ShelterCategoryList.tsx | 57 ++++ .../components/ShelterCategoryList/index.ts | 3 + .../components/ShelterCategoryList/types.ts | 16 + src/pages/Shelter/components/index.ts | 4 +- .../donationOrder/donationOrder.service.ts | 34 ++ src/service/donationOrder/index.ts | 3 + src/service/donationOrder/types.ts | 51 +++ src/service/index.ts | 2 + .../shelterSupply/shelter-supply.service.ts | 26 +- src/service/shelterSupply/types.ts | 23 ++ src/service/users/types.ts | 4 + src/service/users/user.service.ts | 11 +- 56 files changed, 1227 insertions(+), 385 deletions(-) create mode 100644 src/components/DonationCart/DonationCart.tsx create mode 100644 src/components/DonationCart/components/DonationCartForm/DonationCartForm.tsx create mode 100644 src/components/DonationCart/components/DonationCartForm/index.ts create mode 100644 src/components/DonationCart/components/DonationCartForm/types.ts create mode 100644 src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx create mode 100644 src/components/DonationCart/components/DonationSuccess/index.ts create mode 100644 src/components/DonationCart/components/DonationSuccess/types.ts create mode 100644 src/components/DonationCart/components/index.ts create mode 100644 src/components/DonationCart/index.ts create mode 100644 src/components/DonationCart/types.ts create mode 100644 src/components/DonationCartIcon/DonationCartIcon.tsx create mode 100644 src/components/DonationCartIcon/index.ts create mode 100644 src/components/DonationCartIcon/types.ts create mode 100644 src/contexts/DonationCartContext/DonationCartContext.tsx create mode 100644 src/contexts/DonationCartContext/index.ts create mode 100644 src/contexts/DonationCartContext/types.ts create mode 100644 src/hooks/useDonationOrder/index.ts create mode 100644 src/hooks/useDonationOrder/types.ts create mode 100644 src/hooks/useDonationOrder/useDonationOrder.tsx delete mode 100644 src/pages/Shelter/components/ShelterCategoryItems/ShelterCategoryItems.tsx delete mode 100644 src/pages/Shelter/components/ShelterCategoryItems/index.ts delete mode 100644 src/pages/Shelter/components/ShelterCategoryItems/types.ts create mode 100644 src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx create mode 100644 src/pages/Shelter/components/ShelterCategoryList/index.ts create mode 100644 src/pages/Shelter/components/ShelterCategoryList/types.ts create mode 100644 src/service/donationOrder/donationOrder.service.ts create mode 100644 src/service/donationOrder/index.ts create mode 100644 src/service/donationOrder/types.ts diff --git a/.env.example b/.env.example index fa8ac5ee..ad8971f6 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ VITE_API_URL=http://localhost:4000 -VITE_HMAC_SECRET_KEY= \ No newline at end of file +VITE_HMAC_SECRET_KEY= +VITE_REQUEST_CACHE=false \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index f0dd1a25..af7fd636 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,9 +2,9 @@ import { Fragment } from 'react'; import { BrowserRouter } from 'react-router-dom'; import { Routes } from './routes/Routes'; -import { SessionProvider } from './contexts'; +import { DonationCartProvider, SessionProvider } from './contexts'; import { Toaster } from './components/ui/toaster'; -import { BackToTop } from '@/components/BackToTop'; +import { BackToTop } from '@/components'; const App = () => { return ( @@ -12,8 +12,10 @@ const App = () => { - - + + + + diff --git a/src/components/BackToTop/BackToTop.tsx b/src/components/BackToTop/BackToTop.tsx index bc64c207..e1c418de 100644 --- a/src/components/BackToTop/BackToTop.tsx +++ b/src/components/BackToTop/BackToTop.tsx @@ -1,41 +1,45 @@ -import { useState } from "react" -import { ArrowUp } from "lucide-react" +import { useState } from 'react'; +import { ArrowUp } from 'lucide-react'; +const BackToTop = () => { + const [isVisible, setVisibility] = useState(false); -const BackToTop =() => { - - const [isVisible, setVisibility] = useState(false) - - const scrollToTop = () => { - let root = document.getElementById('root') - if (!root) {return} - - root.scrollTo({top:0, behavior:"smooth"}) - + const scrollToTop = () => { + const root = document.getElementById('root'); + if (!root) { + return; } - document.getElementById("root")?.addEventListener('scroll', (e) => { - if (e.target === null) {return} - let CurrentScrollHeight = (e.target as HTMLElement).scrollTop - let WindowHeight = window.innerHeight + root.scrollTo({ top: 0, behavior: 'smooth' }); + }; - if ( CurrentScrollHeight > WindowHeight / 2) { - setVisibility(true) - } else { - setVisibility(false) - } - }) + document.getElementById('root')?.addEventListener('scroll', (e) => { + if (e.target === null) { + return; + } + const CurrentScrollHeight = (e.target as HTMLElement).scrollTop; + const WindowHeight = window.innerHeight; + if (CurrentScrollHeight > WindowHeight / 2) { + setVisibility(true); + } else { + setVisibility(false); + } + }); -return (isVisible && ( - -)); -} + onClick={scrollToTop} + > + + + ) + ); +}; export { BackToTop }; diff --git a/src/components/BurgerMenu/BurgerMenu.tsx b/src/components/BurgerMenu/BurgerMenu.tsx index 2f823e5b..db04d613 100644 --- a/src/components/BurgerMenu/BurgerMenu.tsx +++ b/src/components/BurgerMenu/BurgerMenu.tsx @@ -8,6 +8,7 @@ import { LinkIcon, Menu, ShieldAlert, + X, } from 'lucide-react'; import { SessionServices } from '@/service'; @@ -16,6 +17,9 @@ import { BurguerMenuItem } from './components'; import { Separator } from '../ui/separator'; import { SessionContext } from '@/contexts'; import { usePartners } from '@/hooks'; +import { DialogClose } from '@radix-ui/react-dialog'; +import { Button } from '../ui/button'; +import { DialogFooter } from '../ui/dialog'; const BurgerMenu = () => { const { session } = useContext(SessionContext); @@ -37,7 +41,14 @@ const BurgerMenu = () => { - + + + + + +
{session && ( diff --git a/src/components/DonationCart/DonationCart.tsx b/src/components/DonationCart/DonationCart.tsx new file mode 100644 index 00000000..5fed55bd --- /dev/null +++ b/src/components/DonationCart/DonationCart.tsx @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; + +import { IDonationCart } from './types'; +import { Sheet, SheetContent } from '../ui/sheet'; +import { DonationCartForm, DonationSuccess } from './components'; + +const DonationCart = (props: IDonationCart) => { + const { onClose, opened, shelterId } = props; + const [donationOrderId, setDonationOrderId] = useState(null); + + useEffect(() => { + const el = document.querySelector('header'); + if (el) { + if (opened) { + el?.classList.remove('z-[100]'); + el?.classList.add('z-0'); + } else { + el?.classList.remove('z-0'); + el?.classList.add('z-[100]'); + } + } + }, [opened]); + + useEffect(() => { + if (!opened) setDonationOrderId(null); + }, [opened]); + + return ( + + + {donationOrderId ? ( + + ) : ( + setDonationOrderId(orderId)} + /> + )} + + + ); +}; + +export { DonationCart }; diff --git a/src/components/DonationCart/components/DonationCartForm/DonationCartForm.tsx b/src/components/DonationCart/components/DonationCartForm/DonationCartForm.tsx new file mode 100644 index 00000000..98b90f80 --- /dev/null +++ b/src/components/DonationCart/components/DonationCartForm/DonationCartForm.tsx @@ -0,0 +1,273 @@ +import React, { useCallback, useContext, useMemo, useState } from 'react'; +import { Trash2 } from 'lucide-react'; + +import { + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from '../../../ui/sheet'; +import { DonationCartContext, SessionContext } from '@/contexts'; +import { Input } from '../../../ui/input'; +import { Button } from '../../../ui/button'; +import { SupplyMeasureMap, cn, getSupplyPriorityProps } from '@/lib/utils'; +import { CircleStatus } from '../../../CircleStatus'; +import { Separator } from '../../../ui/separator'; +import { IDonationCartItem } from '@/contexts/DonationCartContext/types'; +import { + DonationOrderServices, + SessionServices, + // ShelterSupplyServices, + UserServices, +} from '@/service'; +import { IDonateItem } from '@/service/donationOrder/types'; +import { TextField } from '../../../TextField'; +import { ICreateUser } from '@/service/users/types'; +import { IDonationCartForm } from './types'; + +const DonationCartForm = React.forwardRef( + (props, ref) => { + const { shelterId, onCancel, onSuccess, className = '', ...rest } = props; + const { refreshSession, session } = useContext(SessionContext); + const { carts, removeItem, clearCart, updateItem } = + useContext(DonationCartContext); + const [loading, setLoading] = useState(false); + const cart = useMemo(() => carts[shelterId] ?? [], [carts, shelterId]); + const [errors, setErrors] = useState>({}); + const [values, setValues] = useState>({}); + + const handleCancelCart = useCallback(() => { + clearCart(shelterId); + if (onCancel) onCancel(); + }, [clearCart, onCancel, shelterId]); + + const handleChangeQuantity = useCallback( + (item: IDonationCartItem, quantity: number) => { + setValues((prev) => ({ + ...prev, + [item.id]: quantity, + })); + updateItem(shelterId, item.id, { quantity }); + }, + [shelterId, updateItem] + ); + + // const verifyCartItems = useCallback( + // async ( + // shelterId: string, + // items: IDonateItem[] + // ): Promise> => { + // const { data } = await ShelterSupplyServices.getAll(shelterId); + // const newErrors = items.reduce((prev, current) => { + // const finded = data.find((d) => d.supply.id === current.id); + // const ok = current.quantity <= (finded?.quantity ?? 0); + // if (ok || !finded) return prev; + // else { + // const measure = SupplyMeasureMap[finded.supply.measure]; + // return { + // ...prev, + // [current.id]: `A doação de ${finded.supply.name} não pode ser maior que a quantidade máxima de ${finded.quantity}${measure} `, + // }; + // } + // }, {}); + // return newErrors; + // }, + // [] + // ); + + const verifyAccountExists = useCallback(async (phone: string) => { + const { data } = await UserServices.find('phone', phone); + if (data.exists) { + setErrors({ + phone: + 'Já existe um usuário com este telefone. Faça login ou tente outro telefone', + }); + return false; + } + return true; + }, []); + + const handleCreateAccount = useCallback( + async (payload: Record) => { + const { lastName, name, phone } = payload; + if (name && lastName && phone) { + const ok = await verifyAccountExists(phone.toString()); + if (!ok) return false; + + await UserServices.create({ + phone: phone.toString(), + name: name.toString(), + lastName: lastName.toString(), + }); + const parsedPhone = phone.toString().replace(/[^0-9]/g, ''); + const resp = await SessionServices.auth({ + login: parsedPhone, + password: parsedPhone, + }); + localStorage.setItem('token', resp.token); + refreshSession(); + } + + return true; + }, + [refreshSession, verifyAccountExists] + ); + + const handleDonate = useCallback( + async (ev: React.FormEvent) => { + ev.preventDefault(); + + setLoading(true); + try { + const form = new FormData(ev.currentTarget); + const formData = Object.fromEntries(form); + const { name, lastName, phone, ...rest } = formData; + const ok = await handleCreateAccount({ name, lastName, phone }); + if (ok) { + const items = Object.entries(rest).reduce( + (prev, [key, value]) => [...prev, { id: key, quantity: +value }], + [] as IDonateItem[] + ); + //TODO: discutir produto se vai e como será verificado os "erros" do carrinho + // const errorsData = await verifyCartItems(shelterId, items); + // setErrors(errorsData); + // if (Object.keys(errorsData).length === 0) { + const resp = await DonationOrderServices.store({ + shelterId, + supplies: items, + }); + if (onSuccess) onSuccess(resp.data.id); + clearCart(shelterId); + // } + } + } catch (err) { + console.log('Ocorreu um erro ao realizar a doação'); + } finally { + setLoading(false); + } + }, + [clearCart, handleCreateAccount, onSuccess, shelterId] + ); + + return ( +
+ + + {[session?.name, 'Revise sua doação'].filter((p) => !!p).join(', ')} + + + Ajuste a quantidade que gostaria de doar em cada item + + +
+
+ Item + Quantidade +
+ {cart.map((item) => { + const { className } = getSupplyPriorityProps(item.priority); + return ( +
+
+
+ + {item.name} +
+
+
+ + handleChangeQuantity(item, +ev.target.value) + } + /> + + {SupplyMeasureMap[item.measure]} + +
+ +
+
+ {errors[item.id] && ( + +

{errors[item.id]}

+
+ )} +
+ ); + })} + + {!session && ( +
+

Comunique sua doação

+

+ Utilizaremos seus dados apenas para comunicar ao abrigo que sua + doação esta a caminho. +

+
+ + + +
+
+ )} +
+ +
+ + +
+
+
+ ); + } +); + +export { DonationCartForm }; diff --git a/src/components/DonationCart/components/DonationCartForm/index.ts b/src/components/DonationCart/components/DonationCartForm/index.ts new file mode 100644 index 00000000..f07b238a --- /dev/null +++ b/src/components/DonationCart/components/DonationCartForm/index.ts @@ -0,0 +1,3 @@ +import { DonationCartForm } from './DonationCartForm'; + +export { DonationCartForm }; diff --git a/src/components/DonationCart/components/DonationCartForm/types.ts b/src/components/DonationCart/components/DonationCartForm/types.ts new file mode 100644 index 00000000..85a28027 --- /dev/null +++ b/src/components/DonationCart/components/DonationCartForm/types.ts @@ -0,0 +1,5 @@ +export interface IDonationCartForm extends React.ComponentPropsWithRef<'form'> { + shelterId: string; + onCancel?: () => void; + onSuccess?: (donationOrderId: string) => void; +} diff --git a/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx b/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx new file mode 100644 index 00000000..6418d37e --- /dev/null +++ b/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Circle, HeartHandshake, Loader, Printer } from 'lucide-react'; + +import { + SheetHeader, + SheetTitle, + SheetDescription, +} from '@/components/ui/sheet'; +import { IDonationSuccessProps } from './types'; +import { SupplyMeasureMap, cn } from '@/lib/utils'; +import { useDonationOrder } from '@/hooks'; +import { format } from 'date-fns'; +import { Button } from '@/components/ui/button'; + +const DonationSuccess = React.forwardRef( + (props, ref) => { + const { donationOrderId, className = '', ...rest } = props; + const { data: donation, loading } = useDonationOrder(donationOrderId); + + if (loading) + return ; + + return ( +
+ + O RS agradece sua doação + + + + +
+
+ + Cada doação importa. + + + Juntos somos mais fortes! + +
+
+
+ + Doação para + +

{donation.shelter.name}

+ + às{' '} + {format(new Date(donation.createdAt), "HH'h'mm 'de' dd/MM/yy")} + +
+
    + {donation.donationOrderSupplies.map((s, idx) => ( +
  • + + {s.quantity} + {SupplyMeasureMap[s.supply.measure]} de {s.supply.name} +
  • + ))} +
+ +
+
+
+ +
+
+ ); + } +); + +export { DonationSuccess }; diff --git a/src/components/DonationCart/components/DonationSuccess/index.ts b/src/components/DonationCart/components/DonationSuccess/index.ts new file mode 100644 index 00000000..aef6420e --- /dev/null +++ b/src/components/DonationCart/components/DonationSuccess/index.ts @@ -0,0 +1,3 @@ +import { DonationSuccess } from './DonationSuccess'; + +export { DonationSuccess }; diff --git a/src/components/DonationCart/components/DonationSuccess/types.ts b/src/components/DonationCart/components/DonationSuccess/types.ts new file mode 100644 index 00000000..12de8593 --- /dev/null +++ b/src/components/DonationCart/components/DonationSuccess/types.ts @@ -0,0 +1,4 @@ +export interface IDonationSuccessProps + extends React.ComponentPropsWithoutRef<'div'> { + donationOrderId: string; +} diff --git a/src/components/DonationCart/components/index.ts b/src/components/DonationCart/components/index.ts new file mode 100644 index 00000000..ffced286 --- /dev/null +++ b/src/components/DonationCart/components/index.ts @@ -0,0 +1,4 @@ +import { DonationCartForm } from './DonationCartForm'; +import { DonationSuccess } from './DonationSuccess'; + +export { DonationCartForm, DonationSuccess }; diff --git a/src/components/DonationCart/index.ts b/src/components/DonationCart/index.ts new file mode 100644 index 00000000..0013386e --- /dev/null +++ b/src/components/DonationCart/index.ts @@ -0,0 +1,3 @@ +import { DonationCart } from './DonationCart'; + +export { DonationCart }; diff --git a/src/components/DonationCart/types.ts b/src/components/DonationCart/types.ts new file mode 100644 index 00000000..bdd8708b --- /dev/null +++ b/src/components/DonationCart/types.ts @@ -0,0 +1,5 @@ +export interface IDonationCart { + shelterId: string; + opened: boolean; + onClose: () => void; +} diff --git a/src/components/DonationCartIcon/DonationCartIcon.tsx b/src/components/DonationCartIcon/DonationCartIcon.tsx new file mode 100644 index 00000000..478e7959 --- /dev/null +++ b/src/components/DonationCartIcon/DonationCartIcon.tsx @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import { HandHeart } from 'lucide-react'; + +import { Button } from '../ui/button'; +import { DonationCartContext } from '@/contexts'; +import { IDonationCartIconProps } from './types'; + +const DonationCartIcon = (props: IDonationCartIconProps) => { + const { quantity } = props; + const { toggleOpened } = useContext(DonationCartContext); + + return ( + + ); +}; + +export { DonationCartIcon }; diff --git a/src/components/DonationCartIcon/index.ts b/src/components/DonationCartIcon/index.ts new file mode 100644 index 00000000..7c7ffa6b --- /dev/null +++ b/src/components/DonationCartIcon/index.ts @@ -0,0 +1,3 @@ +import { DonationCartIcon } from './DonationCartIcon'; + +export { DonationCartIcon }; diff --git a/src/components/DonationCartIcon/types.ts b/src/components/DonationCartIcon/types.ts new file mode 100644 index 00000000..eee4304e --- /dev/null +++ b/src/components/DonationCartIcon/types.ts @@ -0,0 +1,3 @@ +export interface IDonationCartIconProps { + quantity?: number; +} diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 00e1529e..b3255f6d 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,8 +1,8 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import { IHeader } from './types'; import { cn } from '@/lib/utils'; -import { Link } from 'react-router-dom'; const Header = React.forwardRef((props, ref) => { const { @@ -14,10 +14,10 @@ const Header = React.forwardRef((props, ref) => { } = props; return ( -
((props, ref) => { {title}
-
-
{endAdornment}
-
-
+
{endAdornment}
+ ); }); diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx index 225ddb87..cbd723d2 100644 --- a/src/components/SearchInput/SearchInput.tsx +++ b/src/components/SearchInput/SearchInput.tsx @@ -7,21 +7,26 @@ import { cn } from '@/lib/utils'; const SearchInput = React.forwardRef( (props, ref) => { + const { inputProps, value, onChange, className, ...rest } = props; const { - value, - onChange, - className, placeholder = 'Buscar por abrigo ou endereço', - ...rest - } = props; + className: inputClassName = '', + ...restInputProps + } = inputProps ?? {}; return (
+ onChange ? onChange(ev.target.value ?? '') : undefined + } + {...restInputProps} />
diff --git a/src/components/SearchInput/types.ts b/src/components/SearchInput/types.ts index b15517df..9594490e 100644 --- a/src/components/SearchInput/types.ts +++ b/src/components/SearchInput/types.ts @@ -1,4 +1,9 @@ export interface ISearchInputProps - extends React.ComponentPropsWithoutRef<'input'> { + extends Omit, 'onChange'> { value: string; + onChange?: (value: string) => void; + inputProps?: Omit< + React.ComponentPropsWithoutRef<'input'>, + 'value' | 'onChange' + >; } diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index db4fa822..43a2adf0 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -14,7 +14,7 @@ const TextField = forwardRef, TextFieldProps>( className, error, helperText, - value = '', + value, ...rest } = props; diff --git a/src/components/index.ts b/src/components/index.ts index 542ce455..fe877999 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -14,6 +14,9 @@ import { VerifiedBadge } from './VerifiedBadge'; import { SelectField } from './SelectField'; import { Authenticated } from './Authenticated'; import { BurgerMenu } from './BurgerMenu'; +import { BackToTop } from './BackToTop'; +import { DonationCartIcon } from './DonationCartIcon'; +import { DonationCart } from './DonationCart'; export { LoadingScreen, @@ -32,4 +35,7 @@ export { SelectField, Authenticated, BurgerMenu, + BackToTop, + DonationCartIcon, + DonationCart, }; diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index 117f9957..d64a6fa7 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -1,17 +1,17 @@ -import * as React from 'react'; -import * as SheetPrimitive from '@radix-ui/react-dialog'; -import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" -import { cn } from '@/lib/utils'; -import { X } from 'lucide-react'; +import { cn } from "@/lib/utils" -const Sheet = SheetPrimitive.Root; +const Sheet = SheetPrimitive.Root -const SheetTrigger = SheetPrimitive.Trigger; +const SheetTrigger = SheetPrimitive.Trigger -const SheetClose = SheetPrimitive.Close; +const SheetClose = SheetPrimitive.Close -const SheetPortal = SheetPrimitive.Portal; +const SheetPortal = SheetPrimitive.Portal const SheetOverlay = React.forwardRef< React.ElementRef, @@ -19,33 +19,33 @@ const SheetOverlay = React.forwardRef< >(({ className, ...props }, ref) => ( -)); -SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName const sheetVariants = cva( - 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500', + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", { variants: { side: { - top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top', + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", bottom: - 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom', - left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm', + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", right: - 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm', + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", }, }, defaultVariants: { - side: 'right', + side: "right", }, } -); +) interface SheetContentProps extends React.ComponentPropsWithoutRef, @@ -54,7 +54,7 @@ interface SheetContentProps const SheetContent = React.forwardRef< React.ElementRef, SheetContentProps ->(({ side = 'right', className, children, ...props }, ref) => ( +>(({ side = "right", className, children, ...props }, ref) => ( {children} - - + + + Close -)); -SheetContent.displayName = SheetPrimitive.Content.displayName; +)) +SheetContent.displayName = SheetPrimitive.Content.displayName const SheetHeader = ({ className, @@ -77,13 +78,13 @@ const SheetHeader = ({ }: React.HTMLAttributes) => (
-); -SheetHeader.displayName = 'SheetHeader'; +) +SheetHeader.displayName = "SheetHeader" const SheetFooter = ({ className, @@ -91,13 +92,13 @@ const SheetFooter = ({ }: React.HTMLAttributes) => (
-); -SheetFooter.displayName = 'SheetFooter'; +) +SheetFooter.displayName = "SheetFooter" const SheetTitle = React.forwardRef< React.ElementRef, @@ -105,11 +106,11 @@ const SheetTitle = React.forwardRef< >(({ className, ...props }, ref) => ( -)); -SheetTitle.displayName = SheetPrimitive.Title.displayName; +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName const SheetDescription = React.forwardRef< React.ElementRef, @@ -117,11 +118,11 @@ const SheetDescription = React.forwardRef< >(({ className, ...props }, ref) => ( -)); -SheetDescription.displayName = SheetPrimitive.Description.displayName; +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName export { Sheet, @@ -134,4 +135,4 @@ export { SheetFooter, SheetTitle, SheetDescription, -}; +} diff --git a/src/contexts/DonationCartContext/DonationCartContext.tsx b/src/contexts/DonationCartContext/DonationCartContext.tsx new file mode 100644 index 00000000..138bf9f6 --- /dev/null +++ b/src/contexts/DonationCartContext/DonationCartContext.tsx @@ -0,0 +1,99 @@ +import React, { createContext, useCallback, useEffect, useState } from 'react'; + +import { IDonationCartContext, IDonationCartItem } from './types'; + +function getDonationCart(): Record { + const data = localStorage.getItem('shelter-carts'); + if (!data) return {}; + return JSON.parse(data); +} + +const DonationCartContext = createContext({} as IDonationCartContext); + +const DonationCartProvider = ({ children }: { children?: React.ReactNode }) => { + const [opened, setOpened] = useState(false); + const [carts, setCarts] = useState>( + getDonationCart() + ); + + const addItem = useCallback((shelterId: string, item: IDonationCartItem) => { + setCarts((state) => { + const prev = state[shelterId] ?? []; + if (prev.some((p) => p.id === item.id)) + return { + ...state, + [shelterId]: prev.map((p) => + p.id === item.id + ? { ...p, quantity: p.quantity + item.quantity } + : p + ), + }; + else return { ...state, [shelterId]: [...prev, item] }; + }); + }, []); + + const updateItem = useCallback( + ( + shelterId: string, + supplyId: string, + payload: Partial> + ) => { + setCarts((state) => { + const prev = state[shelterId] ?? []; + if (prev.some((p) => p.id === supplyId)) + return { + ...state, + [shelterId]: prev.map((p) => + p.id === supplyId ? { ...p, ...payload } : p + ), + }; + else return state; + }); + }, + [] + ); + + const removeItem = useCallback((shelterId: string, supplyId: string) => { + setCarts((state) => { + const prev = state[shelterId] ?? []; + return { ...state, [shelterId]: prev.filter((p) => p.id !== supplyId) }; + }); + }, []); + + const toggleOpened = useCallback(() => setOpened((prev) => !prev), []); + + const clearCart = useCallback( + (shelterId: string) => setCarts((state) => ({ ...state, [shelterId]: [] })), + [] + ); + + const updateCart = useCallback( + (shelterId: string, items: IDonationCartItem[]) => { + setCarts((prev) => ({ ...prev, [shelterId]: items })); + }, + [] + ); + + useEffect(() => { + localStorage.setItem('shelter-carts', JSON.stringify(carts)); + }, [carts]); + + return ( + + {children} + + ); +}; + +export { DonationCartContext, DonationCartProvider }; diff --git a/src/contexts/DonationCartContext/index.ts b/src/contexts/DonationCartContext/index.ts new file mode 100644 index 00000000..a66d1068 --- /dev/null +++ b/src/contexts/DonationCartContext/index.ts @@ -0,0 +1,6 @@ +import { + DonationCartContext, + DonationCartProvider, +} from './DonationCartContext'; + +export { DonationCartContext, DonationCartProvider }; diff --git a/src/contexts/DonationCartContext/types.ts b/src/contexts/DonationCartContext/types.ts new file mode 100644 index 00000000..14539cb7 --- /dev/null +++ b/src/contexts/DonationCartContext/types.ts @@ -0,0 +1,25 @@ +import { SupplyMeasure } from '@/hooks/useShelter/types'; +import { SupplyPriority } from '@/service/supply/types'; + +export interface IDonationCartItem { + id: string; + name: string; + quantity: number; + priority: SupplyPriority; + measure: SupplyMeasure; +} + +export interface IDonationCartContext { + carts: Record; + opened: boolean; + toggleOpened: () => void; + addItem: (shelterId: string, item: IDonationCartItem) => void; + removeItem: (shelterId: string, supplyId: string) => void; + updateItem: ( + shelterId: string, + supplyId: string, + payload: Partial> + ) => void; + clearCart: (shelterId: string) => void; + updateCart: (shelterId: string, items: IDonationCartItem[]) => void; +} diff --git a/src/contexts/index.ts b/src/contexts/index.ts index 2d3ea5bd..7eeef33b 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -1,3 +1,12 @@ -import { SessionContext, SessionProvider } from "./SessionContext"; +import { SessionContext, SessionProvider } from './SessionContext'; +import { + DonationCartContext, + DonationCartProvider, +} from './DonationCartContext'; -export { SessionContext, SessionProvider }; +export { + SessionContext, + SessionProvider, + DonationCartContext, + DonationCartProvider, +}; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index a8007959..f28c05ed 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -12,6 +12,7 @@ import { usePartners } from './usePartners'; import { useGithubContributors } from './useGithubContributors'; import { useAuthRoles } from './useAuthRoles'; import { useSupporters } from './useSupporters'; +import { useDonationOrder } from './useDonationOrder'; export { useShelters, @@ -28,4 +29,5 @@ export { useGithubContributors, useAuthRoles, useSupporters, + useDonationOrder, }; diff --git a/src/hooks/useDonationOrder/index.ts b/src/hooks/useDonationOrder/index.ts new file mode 100644 index 00000000..f0211436 --- /dev/null +++ b/src/hooks/useDonationOrder/index.ts @@ -0,0 +1,3 @@ +import { useDonationOrder } from './useDonationOrder'; + +export { useDonationOrder }; diff --git a/src/hooks/useDonationOrder/types.ts b/src/hooks/useDonationOrder/types.ts new file mode 100644 index 00000000..e15144de --- /dev/null +++ b/src/hooks/useDonationOrder/types.ts @@ -0,0 +1,57 @@ +export enum ShelterCategory { + Shelter = 'Shelter', + DistributionCenter = 'DistributionCenter', +} + +export enum SupplyMeasure { + Unit = 'Unit', + Kg = 'Kg', + Litters = 'Litters', + Box = 'Box', + Piece = 'Piece', +} + +export interface IUseShelterData { + id: string; + name: string; + street?: string; + neighbourhood?: string; + city?: string; + streetNumber?: string | null; + zipCode?: string; + address: string; + pix?: string | null; + shelteredPeople?: number | null; + capacity?: number | null; + contact?: string | null; + petFriendly?: boolean | null; + prioritySum: number; + verified: boolean; + latitude?: string | null; + longitude?: string | null; + shelterSupplies: IUseShelterDataSupply[]; + category: ShelterCategory; + actived: boolean; + createdAt: string; + updatedAt?: string | null; +} + +export interface IUseShelterDataSupply { + priority: number; + quantity?: number | null; + supply: IUseShelterDataSupplyData; +} + +export interface IUseShelterDataSupplyData { + id: string; + name: string; + measure: SupplyMeasure; + supplyCategory: IUseShelterDataSupplyCategory; + createdAt: string; + updatedAt?: string | null; +} + +export interface IUseShelterDataSupplyCategory { + id: string; + name: string; +} diff --git a/src/hooks/useDonationOrder/useDonationOrder.tsx b/src/hooks/useDonationOrder/useDonationOrder.tsx new file mode 100644 index 00000000..f80513fe --- /dev/null +++ b/src/hooks/useDonationOrder/useDonationOrder.tsx @@ -0,0 +1,8 @@ +import { IDonateOrderItem } from '@/service/donationOrder/types'; +import { useFetch } from '../useFetch'; + +const useDonationOrder = (donationOrderId: string) => { + return useFetch(`/donation/order/${donationOrderId}`); +}; + +export { useDonationOrder }; diff --git a/src/hooks/useFetch/useFetch.tsx b/src/hooks/useFetch/useFetch.tsx index 121f5795..887833bd 100644 --- a/src/hooks/useFetch/useFetch.tsx +++ b/src/hooks/useFetch/useFetch.tsx @@ -13,7 +13,8 @@ function useFetch(path?: string, options: IUseFetchOptions = {}) { const refresh = useCallback( (config?: AxiosRequestConfig) => { const headers = config?.headers ?? {}; - if (cache) headers['x-app-cache'] = 'true'; + if (cache && import.meta.env.VITE_REQUEST_CACHE !== 'false') + headers['x-app-cache'] = 'true'; setLoading(true); if (path) { diff --git a/src/hooks/usePaginatedQuery/paths.ts b/src/hooks/usePaginatedQuery/paths.ts index 5c1d2cc5..6dde3171 100644 --- a/src/hooks/usePaginatedQuery/paths.ts +++ b/src/hooks/usePaginatedQuery/paths.ts @@ -3,4 +3,5 @@ export enum PaginatedQueryPath { ShelterCities = '/shelters/cities', SupplyCategories = '/supply-categories', Supplies = '/supplies', + DonationOrder = '/donation/order', } diff --git a/src/hooks/usePaginatedQuery/usePaginatedQuery.tsx b/src/hooks/usePaginatedQuery/usePaginatedQuery.tsx index 11009abd..4229807e 100644 --- a/src/hooks/usePaginatedQuery/usePaginatedQuery.tsx +++ b/src/hooks/usePaginatedQuery/usePaginatedQuery.tsx @@ -6,7 +6,10 @@ import { PaginatedQueryPath } from './paths'; import { IPaginatedResponse } from './types'; import { IServerResponse } from '@/types'; -function usePaginatedQuery(path: string | PaginatedQueryPath) { +function usePaginatedQuery( + path: string | PaginatedQueryPath, + defaultConfig: AxiosRequestConfig = {} +) { const [loading, setLoading] = useState(false); const [data, setData] = useState>({ count: 0, @@ -16,14 +19,17 @@ function usePaginatedQuery(path: string | PaginatedQueryPath) { }); const refresh = useCallback( - (config?: AxiosRequestConfig) => { + (config: AxiosRequestConfig = {}) => { setLoading(true); api - .get>>(path, config) + .get>>(path, { + ...defaultConfig, + ...config, + }) .then(({ data }) => setData(data.data)) .finally(() => setLoading(false)); }, - [path] + [defaultConfig, path] ); useEffect(() => { diff --git a/src/hooks/useShelter/types.ts b/src/hooks/useShelter/types.ts index 22c3bd67..e15144de 100644 --- a/src/hooks/useShelter/types.ts +++ b/src/hooks/useShelter/types.ts @@ -3,6 +3,14 @@ export enum ShelterCategory { DistributionCenter = 'DistributionCenter', } +export enum SupplyMeasure { + Unit = 'Unit', + Kg = 'Kg', + Litters = 'Litters', + Box = 'Box', + Piece = 'Piece', +} + export interface IUseShelterData { id: string; name: string; @@ -37,6 +45,7 @@ export interface IUseShelterDataSupply { export interface IUseShelterDataSupplyData { id: string; name: string; + measure: SupplyMeasure; supplyCategory: IUseShelterDataSupplyCategory; createdAt: string; updatedAt?: string | null; diff --git a/src/hooks/useShelters/types.ts b/src/hooks/useShelters/types.ts index 057bfcbe..4ad161e6 100644 --- a/src/hooks/useShelters/types.ts +++ b/src/hooks/useShelters/types.ts @@ -1,5 +1,5 @@ import { ShelterTagType } from '@/pages/Home/components/ShelterListItem/types'; -import { ShelterCategory } from '../useShelter/types'; +import { ShelterCategory, SupplyMeasure } from '../useShelter/types'; export interface IUseSheltersData { id: string; @@ -29,6 +29,7 @@ export interface IUseSheltersData { export interface IUseSheltersDataSupplyData { supply: { name: string; + measure: SupplyMeasure; supplyCategory: { name: string }; }; priority: number; diff --git a/src/hooks/useShelters/useShelters.tsx b/src/hooks/useShelters/useShelters.tsx index f6959800..13e51ac4 100644 --- a/src/hooks/useShelters/useShelters.tsx +++ b/src/hooks/useShelters/useShelters.tsx @@ -20,7 +20,8 @@ const useShelters = (options: IUseShelterOptions = {}) => { (config: AxiosRequestConfig = {}, append: boolean = false) => { const { search, ...rest } = (config ?? {}).params ?? {}; const headers = config.headers ?? {}; - if (cache) headers['x-app-cache'] = 'true'; + if (cache && import.meta.env.VITE_REQUEST_CACHE !== 'false') + headers['x-app-cache'] = 'true'; if (!append) setLoading(true); api .get>('/shelters', { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c12c1b59..42513669 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,4 @@ -import { ShelterCategory } from '@/hooks/useShelter/types'; +import { ShelterCategory, SupplyMeasure } from '@/hooks/useShelter/types'; import { IUseSheltersDataSupplyData } from '@/hooks/useShelters/types'; import { ShelterTagInfo, @@ -150,6 +150,14 @@ function checkIsNull(v?: any | null) { return v !== null && v !== undefined; } +const SupplyMeasureMap: Record = { + Box: 'caixa(s)', + Kg: 'kg', + Litters: 'litro(s)', + Piece: 'peça(s)', + Unit: 'un', +}; + export { cn, getAvailabilityProps, @@ -160,4 +168,5 @@ export { removeDuplicatesByField, normalizedCompare, checkIsNull, + SupplyMeasureMap, }; diff --git a/src/pages/Home/components/Filter/Filter.tsx b/src/pages/Home/components/Filter/Filter.tsx index d041a5d4..d19ebf1b 100644 --- a/src/pages/Home/components/Filter/Filter.tsx +++ b/src/pages/Home/components/Filter/Filter.tsx @@ -157,9 +157,7 @@ const Filter = (props: IFilterProps) => {
- setFieldValue('search', ev.target.value ?? '') - } + onChange={(v) => setFieldValue('search', v)} />
diff --git a/src/pages/Home/components/ShelterListView/ShelterListView.tsx b/src/pages/Home/components/ShelterListView/ShelterListView.tsx index ec57fc7b..332998b4 100644 --- a/src/pages/Home/components/ShelterListView/ShelterListView.tsx +++ b/src/pages/Home/components/ShelterListView/ShelterListView.tsx @@ -46,10 +46,8 @@ const ShelterListView = React.forwardRef( /> - onSearchValueChange - ? onSearchValueChange(ev.target.value ?? '') - : undefined + onChange={(value) => + onSearchValueChange ? onSearchValueChange(value) : undefined } />
diff --git a/src/pages/Shelter/Shelter.tsx b/src/pages/Shelter/Shelter.tsx index 78fea039..7b41487c 100644 --- a/src/pages/Shelter/Shelter.tsx +++ b/src/pages/Shelter/Shelter.tsx @@ -1,51 +1,51 @@ -import { useCallback, useMemo, useState } from 'react'; +import { Fragment, useCallback, useContext, useMemo, useState } from 'react'; import { ChevronLeft, Pencil } from 'lucide-react'; import { useNavigate, useParams } from 'react-router-dom'; -import { format } from 'date-fns'; import { Authenticated, CardAboutShelter, + Chip, + DonationCart, + DonationCartIcon, Header, LoadingScreen, + SearchInput, } from '@/components'; import { useShelter } from '@/hooks'; -import { IShelterAvailabilityProps } from '@/pages/Home/components/ShelterListItem/types'; -import { cn, getAvailabilityProps, group } from '@/lib/utils'; +import { + cn, + getAvailabilityProps, + getSupplyPriorityProps, + group, + normalizedCompare, +} from '@/lib/utils'; import { Button } from '@/components/ui/button'; -import { ShelterCategoryItems } from './components'; +import { VerifiedBadge } from '@/components/VerifiedBadge/VerifiedBadge.tsx'; import { - IShelterCategoryItemsProps, - ITagItem, -} from './components/ShelterCategoryItems/types'; + IUseShelterDataSupply, + ShelterCategory, +} from '@/hooks/useShelter/types'; +import { IShelterAvailabilityProps } from '../Home/components/ShelterListItem/types'; import { SupplyPriority } from '@/service/supply/types'; -import { VerifiedBadge } from '@/components/VerifiedBadge/VerifiedBadge.tsx'; -import { ShelterSupplyServices } from '@/service'; -import { useToast } from '@/components/ui/use-toast'; -import { clearCache } from '@/api/cache'; -import { ShelterCategory } from '@/hooks/useShelter/types'; +import { ShelterCategoryList } from './components'; +import { Separator } from '@/components/ui/separator'; +import { DonationCartContext } from '@/contexts'; +import { ShelterCategoryListItemProps } from './components/ShelterCategoryList/types'; + +const defaultPriorities: SupplyPriority[] = [ + SupplyPriority.Urgent, + SupplyPriority.Needing, + SupplyPriority.Remaining, +]; const Shelter = () => { const params = useParams(); const { shelterId = '-1' } = params; const navigate = useNavigate(); - const { data: shelter, loading, refresh } = useShelter(shelterId); - const [selectedTags, setSelectedTags] = useState([]); - const shelterCategories: IShelterCategoryItemsProps[] = useMemo(() => { - const grouped = group(shelter?.shelterSupplies ?? [], 'priority'); - delete grouped[SupplyPriority.NotNeeded]; - - return Object.entries(grouped) - .sort(([a], [b]) => (+a > +b ? -1 : 1)) - .map(([key, values]) => ({ - priority: +key, - tags: values.map((v) => ({ - label: v.supply.name, - value: v.supply.id, - quantity: v.quantity, - })), - })); - }, [shelter?.shelterSupplies]); + const { toggleOpened, addItem, opened, carts } = + useContext(DonationCartContext); + const { data: shelter, loading } = useShelter(shelterId); const { availability, className: availabilityClassName } = useMemo( () => @@ -56,130 +56,170 @@ const Shelter = () => { }), [shelter?.capacity, shelter?.shelteredPeople, shelter?.category] ); - const [loadingUpdateMany, setLoadingUpdateMany] = useState(false); - const { toast } = useToast(); + const [priorities, setPriorities] = + useState(defaultPriorities); + const [search, setSearch] = useState(''); + + const supplyGroups = useMemo(() => { + if (!shelter?.shelterSupplies) return {}; + const groups = group(shelter.shelterSupplies, 'supply.supplyCategory.name'); + return Object.entries(groups).reduce((prev, [name, list]) => { + const filtered = list.filter( + (l) => + priorities.includes(l.priority) && + (!search || normalizedCompare(l.supply.name, search)) + ); + if (filtered.length > 0) return { [name]: filtered, ...prev }; + else return prev; + }, {} as Record); + }, [shelter, priorities, search]); - const handleSelectTag = useCallback((v: ITagItem) => { - setSelectedTags((prev) => - prev.includes(v) ? prev.filter((p) => p.value !== v.value) : [...prev, v] + const handleSelectPriority = (priority: SupplyPriority) => { + setPriorities((prev) => + prev.includes(priority) + ? prev.filter((p) => p !== priority) + : [...prev, priority] ); - }, []); + }; - const handleUpdateMany = useCallback(() => { - setLoadingUpdateMany(true); - ShelterSupplyServices.updateMany( - shelterId, - selectedTags.map((s) => s.value) - ) - .then(() => { - toast({ - title: 'Atualizado com sucesso', - }); - clearCache(false); - refresh(); - setSelectedTags([]); - }) - .catch((err) => { - toast({ - title: 'Erro ao atualizar', - description: `${err?.response?.data?.message ?? err?.message ?? err}`, - }); - }) - .finally(() => { - setLoadingUpdateMany(false); + const handleDonate = useCallback( + (item: ShelterCategoryListItemProps) => { + if (!opened) { + const hasViewedCart = + localStorage.getItem('has-viewed-cart') === 'true'; + if (!hasViewedCart) { + localStorage.setItem('has-viewed-cart', 'true'); + toggleOpened(); + } + } + addItem(shelterId, { + ...item, + quantity: item.quantity || 1, }); - }, [refresh, selectedTags, shelterId, toast]); + }, + [addItem, opened, shelterId, toggleOpened] + ); if (loading) return ; return ( -
-
navigate('/')} - > - - - } + + -
-
-

- {shelter.name} -

- {shelter.verified && } -
-
-

- {availability} -

- +
+
navigate(`/abrigo/${shelterId}/atualizar`)} + className="[&_svg]:stroke-white disabled:bg-red-500 hover:bg-red-400" + onClick={() => navigate('/')} > - Editar - + - -
-
- -
-
-

Itens do abrigo

-
- + +
-
-
- {shelterCategories.map((categoryProps, idx) => ( - + +
+
+

Itens do abrigo

+
+ +
+
+
+ setSearch(value)} + inputProps={{ + placeholder: 'Digite o item a doar', + }} /> - ))} -
- {shelter.updatedAt && ( -
- - Atualizado em {format(shelter.updatedAt, 'dd/MM/yyyy HH:mm')} -
- )} - -
- +
+ {defaultPriorities.map((priority, idx) => { + const { label, className } = getSupplyPriorityProps(priority); + return ( + handleSelectPriority(priority)} + /> + ); + })} +
+
+ {Object.entries(supplyGroups) + .sort((a, b) => (a[0] > b[0] ? 1 : -1)) + .map(([name, list], idx, arr) => { + const isLastElement = idx === arr.length - 1; + return ( + + ({ + id: l.supply.id, + measure: l.supply.measure, + name: l.supply.name, + priority: l.priority, + quantity: l.quantity, + }))} + onDonate={handleDonate} + /> + {!isLastElement && } + + ); + })}
- +
-
+
); }; diff --git a/src/pages/Shelter/components/ShelterCategoryItems/ShelterCategoryItems.tsx b/src/pages/Shelter/components/ShelterCategoryItems/ShelterCategoryItems.tsx deleted file mode 100644 index edc16d09..00000000 --- a/src/pages/Shelter/components/ShelterCategoryItems/ShelterCategoryItems.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useContext, useMemo, useState } from 'react'; -import { ChevronDown, ChevronUp } from 'lucide-react'; -import { cva } from 'class-variance-authority'; - -import { IShelterCategoryItemsProps } from './types'; -import { cn, getSupplyPriorityProps } from '@/lib/utils'; -import { CircleStatus, Chip } from '@/components'; -import { Button } from '@/components/ui/button'; -import { SupplyPriority } from '@/service/supply/types'; -import { SessionContext } from '@/contexts'; -import { Badge } from '@/components/ui/badge'; - -const ShelterCategoryItems = (props: IShelterCategoryItemsProps) => { - const { - priority = SupplyPriority.NotNeeded, - tags, - onSelectTag, - selectedTags = [], - } = props; - const { session } = useContext(SessionContext); - const [opened, setOpened] = useState(false); - const maxVisibleSupplies: number = 10; - const visibleSupplies = useMemo( - () => (opened ? tags : tags.slice(0, maxVisibleSupplies)), - [opened, tags] - ); - const { className: circleClassName, label } = useMemo( - () => getSupplyPriorityProps(priority), - [priority] - ); - - const Icon = opened ? ChevronUp : ChevronDown; - const btnLabel = opened ? 'Ver menos' : 'Ver todos'; - - const variants = cva('cursor-pointer', { - variants: { - variant: { - selected: 'border-4 border-blue-300', - default: 'border-4 border-gray-100', - }, - }, - defaultVariants: { - variant: 'default', - }, - }); - - return ( -
-
- -

- {label} ({tags.length}) -

-
-
- {visibleSupplies.map((tag, idx) => { - const tagProps = - session && - ['DistributionCenter', 'Admin'].includes(session.accessLevel) - ? { - onClick: () => (onSelectTag ? onSelectTag(tag) : undefined), - className: variants({ - className: circleClassName, - variant: selectedTags.includes(tag) - ? 'selected' - : 'default', - }), - } - : { - className: circleClassName, - }; - return ( -
- - {tag.quantity !== null && - tag.quantity !== undefined && - tag.quantity > 0 && ( - - {tag.quantity > 99 ? '99+' : tag.quantity} - - )} -
- ); - })} -
- - {tags.length > maxVisibleSupplies && ( -
- -
- )} -
- ); -}; - -export { ShelterCategoryItems }; diff --git a/src/pages/Shelter/components/ShelterCategoryItems/index.ts b/src/pages/Shelter/components/ShelterCategoryItems/index.ts deleted file mode 100644 index 78d7838d..00000000 --- a/src/pages/Shelter/components/ShelterCategoryItems/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ShelterCategoryItems } from './ShelterCategoryItems'; - -export { ShelterCategoryItems }; diff --git a/src/pages/Shelter/components/ShelterCategoryItems/types.ts b/src/pages/Shelter/components/ShelterCategoryItems/types.ts deleted file mode 100644 index ebb2caa8..00000000 --- a/src/pages/Shelter/components/ShelterCategoryItems/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { SupplyPriority } from '@/service/supply/types'; - -export interface ITagItem { - label: string; - value: string; - quantity?: number | null; -} - -export interface IShelterCategoryItemsProps { - priority?: SupplyPriority; - tags: ITagItem[]; - selectedTags?: ITagItem[]; - onSelectTag?: (v: ITagItem) => void; -} diff --git a/src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx b/src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx new file mode 100644 index 00000000..a5ac597a --- /dev/null +++ b/src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx @@ -0,0 +1,57 @@ +import clsx from 'clsx'; + +import { SupplyMeasureMap, cn, getSupplyPriorityProps } from '@/lib/utils'; +import { ShelterCategoryListProps } from './types'; +import { CircleStatus } from '@/components'; +import { SupplyPriority } from '@/service/supply/types'; + +const ShelterCategoryList = (props: ShelterCategoryListProps) => { + const { items, name, onDonate } = props; + + return ( +
+
+ {name} ({items.length} items) +
+
+ {items + .sort((a, b) => b.priority - a.priority) + .map((item) => { + const { className } = getSupplyPriorityProps(item.priority); + return ( +
+
+ + {item.name} +
+
+ {item.quantity && ( + {`${ + item.quantity + } ${SupplyMeasureMap[item.measure]}`} + )} + +
+
+ ); + })} +
+
+ ); +}; + +export { ShelterCategoryList }; diff --git a/src/pages/Shelter/components/ShelterCategoryList/index.ts b/src/pages/Shelter/components/ShelterCategoryList/index.ts new file mode 100644 index 00000000..eb8b93f0 --- /dev/null +++ b/src/pages/Shelter/components/ShelterCategoryList/index.ts @@ -0,0 +1,3 @@ +import { ShelterCategoryList } from './ShelterCategoryList'; + +export { ShelterCategoryList }; diff --git a/src/pages/Shelter/components/ShelterCategoryList/types.ts b/src/pages/Shelter/components/ShelterCategoryList/types.ts new file mode 100644 index 00000000..2c37df3a --- /dev/null +++ b/src/pages/Shelter/components/ShelterCategoryList/types.ts @@ -0,0 +1,16 @@ +import { SupplyMeasure } from '@/hooks/useShelter/types'; +import { SupplyPriority } from '@/service/supply/types'; + +export interface ShelterCategoryListItemProps { + id: string; + name: string; + quantity?: number | null; + priority: SupplyPriority; + measure: SupplyMeasure; +} + +export interface ShelterCategoryListProps { + name: string; + onDonate: (item: ShelterCategoryListItemProps) => void; + items: ShelterCategoryListItemProps[]; +} diff --git a/src/pages/Shelter/components/index.ts b/src/pages/Shelter/components/index.ts index 78d7838d..eb8b93f0 100644 --- a/src/pages/Shelter/components/index.ts +++ b/src/pages/Shelter/components/index.ts @@ -1,3 +1,3 @@ -import { ShelterCategoryItems } from './ShelterCategoryItems'; +import { ShelterCategoryList } from './ShelterCategoryList'; -export { ShelterCategoryItems }; +export { ShelterCategoryList }; diff --git a/src/service/donationOrder/donationOrder.service.ts b/src/service/donationOrder/donationOrder.service.ts new file mode 100644 index 00000000..717fd2c9 --- /dev/null +++ b/src/service/donationOrder/donationOrder.service.ts @@ -0,0 +1,34 @@ +import { api } from '@/api'; +import { + ICreateDonateResponse, + ICreateDonationOrderProps, + IDonateOrderItem, +} from './types'; +import { IServerResponse } from '@/types'; +import { IPaginatedResponse } from '@/hooks/usePaginatedQuery/types'; + +const DonationOrderServices = { + store: async (payload: ICreateDonationOrderProps) => { + const { data } = await api.post>( + '/donation/order', + payload + ); + return data; + }, + getAll: async (shelterId: string) => { + const { data } = await api.get< + IServerResponse> + >('/donation/order', { + params: { shelterId }, + }); + return data; + }, + find: async (id: string) => { + const { data } = await api.get>( + `/donation/order/${id}` + ); + return data; + }, +}; + +export { DonationOrderServices }; diff --git a/src/service/donationOrder/index.ts b/src/service/donationOrder/index.ts new file mode 100644 index 00000000..0ba66e4a --- /dev/null +++ b/src/service/donationOrder/index.ts @@ -0,0 +1,3 @@ +import { DonationOrderServices } from './donationOrder.service'; + +export { DonationOrderServices }; diff --git a/src/service/donationOrder/types.ts b/src/service/donationOrder/types.ts new file mode 100644 index 00000000..cad3c9b7 --- /dev/null +++ b/src/service/donationOrder/types.ts @@ -0,0 +1,51 @@ +import { SupplyMeasure } from '@/hooks/useShelter/types'; + +export interface IDonateItem { + id: string; + quantity: number; +} + +export interface ICreateDonationOrderProps { + shelterId: string; + supplies: IDonateItem[]; +} + +export interface ICreateDonateResponse { + id: string; + userId: string; + shelterId: string; + status: string; + createdAt: string; + updatedAt?: string | null; +} + +export enum DonateOrderStatus { + Pending = 'Pending', + Canceled = 'Canceled', + Complete = 'Complete', +} + +export interface IDonateOrderItem { + id: string; + status: DonateOrderStatus; + userId: string; + shelter: IDonateOrderShelter; + donationOrderSupplies: IDonateOrderItemSupply[]; + createdAt: string; + updatedAt?: string | null; +} + +export interface IDonateOrderItemSupply { + quantity: number; + supply: IDonateOrderSupply; +} + +export interface IDonateOrderShelter { + id: string; + name: string; +} + +export interface IDonateOrderSupply { + name: string; + measure: SupplyMeasure; +} diff --git a/src/service/index.ts b/src/service/index.ts index b9e629ca..50795f9d 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -3,6 +3,7 @@ import { ShelterServices } from './shelter'; import { SupplyServices } from './supply'; import { ShelterSupplyServices } from './shelterSupply'; import { UserServices } from './users'; +import { DonationOrderServices } from './donationOrder'; export { UserServices, @@ -10,4 +11,5 @@ export { SupplyServices, ShelterServices, ShelterSupplyServices, + DonationOrderServices, }; diff --git a/src/service/shelterSupply/shelter-supply.service.ts b/src/service/shelterSupply/shelter-supply.service.ts index aef057cb..04c617f6 100644 --- a/src/service/shelterSupply/shelter-supply.service.ts +++ b/src/service/shelterSupply/shelter-supply.service.ts @@ -1,6 +1,10 @@ import { api } from '@/api'; import { IServerResponse } from '@/types'; -import { ICreateShelterSupply, IUpdateShelterSupply } from './types'; +import { + ICreateShelterSupply, + IShelterSupplyData, + IUpdateShelterSupply, +} from './types'; const ShelterSupplyServices = { update: async ( @@ -14,22 +18,18 @@ const ShelterSupplyServices = { ); return data; }, - updateMany: async ( - shelterId: string, - supplyIds: string[] - ): Promise => { - const { data } = await api.put( - `/shelter/supplies/${shelterId}/supplies/many`, - { - ids: supplyIds, - } - ); - return data; - }, create: async (payload: ICreateShelterSupply): Promise => { const { data } = await api.post('/shelter/supplies', payload); return data; }, + getAll: async ( + shelterId: string + ): Promise> => { + const { data } = await api.get>( + `/shelter/supplies/${shelterId}` + ); + return data; + }, }; export { ShelterSupplyServices }; diff --git a/src/service/shelterSupply/types.ts b/src/service/shelterSupply/types.ts index 6c4d48ba..c41da483 100644 --- a/src/service/shelterSupply/types.ts +++ b/src/service/shelterSupply/types.ts @@ -1,3 +1,4 @@ +import { SupplyMeasure } from '@/hooks/useShelter/types'; import { SupplyPriority } from '../supply/types'; export interface IShelterSupply { @@ -18,3 +19,25 @@ export type ICreateShelterSupply = Pick< IShelterSupply, 'priority' | 'shelterId' | 'supplyId' | 'quantity' >; + +export interface IShelterSupplyData { + priority: number; + quantity: number; + supply: IShelterSupplyDataSupply; + createdAt: string; + updatedAt?: string | null; +} + +export interface IShelterSupplyDataSupply { + id: string; + name: string; + measure: SupplyMeasure; + supplyCategory: IShelterSupplyDataSupplyCategory; + createdAt: string; + updatedAt?: string | null; +} + +export interface IShelterSupplyDataSupplyCategory { + id: string; + name: string; +} diff --git a/src/service/users/types.ts b/src/service/users/types.ts index 3977b590..28e76bfa 100644 --- a/src/service/users/types.ts +++ b/src/service/users/types.ts @@ -22,3 +22,7 @@ export interface ICreateUser { lastName: string; phone: string; } + +export interface IFindUserResponse { + exists: boolean; +} diff --git a/src/service/users/user.service.ts b/src/service/users/user.service.ts index a5d2f296..5e29658a 100644 --- a/src/service/users/user.service.ts +++ b/src/service/users/user.service.ts @@ -1,7 +1,7 @@ import { api } from '../../api'; import { IServerResponse } from '@/types'; -import { ICreateUser, IUpdateUser } from './types'; +import { ICreateUser, IFindUserResponse, IUpdateUser, IUser } from './types'; const UserServices = { create: async (payload: ICreateUser): Promise => { @@ -18,6 +18,15 @@ const UserServices = { ); return data; }, + find: async ( + field: keyof IUser, + value: string + ): Promise> => { + const { data } = await api.get>( + `/users/find/${field}/${value}` + ); + return data; + }, }; export { UserServices }; From 6af7dd7d90af2d5ea268da59b175d8ba488d4287 Mon Sep 17 00:00:00 2001 From: Vinicius de Andrade Date: Sat, 1 Jun 2024 21:07:36 -0300 Subject: [PATCH 17/37] fix/ replaced all native emoji occurences with lucide icons (#304) --- src/pages/PrivacyPolicy/PrivacyPolicy.tsx | 75 +++++++++++++++-------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/src/pages/PrivacyPolicy/PrivacyPolicy.tsx b/src/pages/PrivacyPolicy/PrivacyPolicy.tsx index d7de826b..ed82a0c1 100644 --- a/src/pages/PrivacyPolicy/PrivacyPolicy.tsx +++ b/src/pages/PrivacyPolicy/PrivacyPolicy.tsx @@ -1,4 +1,5 @@ import { BurgerMenu, Header } from '@/components'; +import { LifeBuoy } from 'lucide-react'; const PrivacyPolicy = () => { return ( @@ -9,11 +10,15 @@ const PrivacyPolicy = () => { Política de privacidade

- Ao entrar para o SOS RS 🛟, você nos confia uma série de - informações. E nós cuidamos e protegemos essas informações, para você. - Aqui você vai entender quais informações coletamos e o porquê de - fazermos isso. E, ainda, como você pode atualizar, acompanhar ou, até - mesmo, excluir esses dados. + Ao entrar para o SOS RS + {' '} + , você nos confia uma série de informações. E nós cuidamos e + protegemos essas informações, para você. Aqui você vai entender quais + informações coletamos e o porquê de fazermos isso. E, ainda, como você + pode atualizar, acompanhar ou, até mesmo, excluir esses dados.

Desenvolvemos um mapa dinâmico que consolida diversas informações @@ -24,9 +29,14 @@ const PrivacyPolicy = () => { deles.

- O SOS RS 🛟 declara manter sigilo e confidencialidade sobre os - dados pessoais cadastrados, mas, como a plataforma é alimentada por - toda a comunidade, não nos responsabilizamos pela: + O SOS RS + {' '} + declara manter sigilo e confidencialidade sobre os dados pessoais + cadastrados, mas, como a plataforma é alimentada por toda a + comunidade, não nos responsabilizamos pela:

  • 1. Veracidade das informações;
  • @@ -44,7 +54,11 @@ const PrivacyPolicy = () => {

    Coletamos apenas informações imprescindíveis para desempenhar nosso propósito. As informações coletadas e como essas informações são - utilizadas dependem de como e do quanto você utiliza o SOS RS 🛟 + utilizadas dependem de como e do quanto você utiliza o SOS RS + {' '} . Armazenamos as informações que coletamos.

    @@ -101,10 +115,15 @@ const PrivacyPolicy = () => { solicitação governamental ou judicial não abusiva.

    - Se o SOS RS 🛟 realizar fusão, aquisição ou venda, continuará a - garantir a confidencialidade das suas informações pessoais e avisará - os afetados antes que as informações sejam transferidas ou submetidas - a uma política de privacidade diferente. + Se o SOS RS + {' '} + realizar fusão, aquisição ou venda, continuará a garantir a + confidencialidade das suas informações pessoais e avisará os afetados + antes que as informações sejam transferidas ou submetidas a uma + política de privacidade diferente.

    Mantemos suas informações seguras @@ -143,13 +162,17 @@ const PrivacyPolicy = () => {

    Eventualmente os dados serão descartados após o encerramento do - propósito do SOS RS 🛟. A nossa política de exclusão garante que - os dados fornecidos sejam removidos de forma segura e completa dos - nossos servidores. Para manter as suas informações protegidas em caso - de exclusão acidental, pode haver um espaço de tempo entre o momento - em que você solicita excluir ou exclui algo e o momento em que as - cópias são definitivamente excluídas dos nossos sistemas ativos e de - backup. + propósito do SOS RS + {' '} + . A nossa política de exclusão garante que os dados fornecidos sejam + removidos de forma segura e completa dos nossos servidores. Para + manter as suas informações protegidas em caso de exclusão acidental, + pode haver um espaço de tempo entre o momento em que você solicita + excluir ou exclui algo e o momento em que as cópias são + definitivamente excluídas dos nossos sistemas ativos e de backup.

    Você pode, a qualquer momento: @@ -171,10 +194,14 @@ const PrivacyPolicy = () => { Quando esta política se aplica e alterações

    - Esta Política de Privacidade se aplica ao SOS RS 🛟. Nos - reservamos ao direito de alterar este documento periodicamente, porém - não reduziremos seus direitos nesta Política de Privacidade, sem seu - consentimento e aviso prévio. + Esta Política de Privacidade se aplica ao SOS RS + {' '} + . Nos reservamos ao direito de alterar este documento periodicamente, + porém não reduziremos seus direitos nesta Política de Privacidade, sem + seu consentimento e aviso prévio.

From 37cc2ecc07d76ac1114e99e2426958778a340228 Mon Sep 17 00:00:00 2001 From: Felipe Maciel Ramos Vieira Date: Sun, 2 Jun 2024 13:50:34 -0300 Subject: [PATCH 18/37] =?UTF-8?q?fix:=20bot=C3=A3o=20"atualizar"=20de=20at?= =?UTF-8?q?ualizar=20abrigo=20aparece=20cortado=20para=20fora=20da=20tela?= =?UTF-8?q?=20-=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.tsx | 13 +++++++++---- src/pages/UpdateShelter/UpdateShelter.tsx | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main.tsx b/src/main.tsx index 471f7fc0..cfc80cbd 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,10 @@ -import ReactDOM from "react-dom/client"; -import { App } from "./App.tsx"; -import "./global.css"; +import ReactDOM from 'react-dom/client'; +import { App } from './App.tsx'; +import './global.css'; -ReactDOM.createRoot(document.getElementById("root")!).render(); +document.documentElement.style.setProperty( + '--vh', + `${window.innerHeight * 0.01}px` +); + +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/src/pages/UpdateShelter/UpdateShelter.tsx b/src/pages/UpdateShelter/UpdateShelter.tsx index c1c8f739..3a428f42 100644 --- a/src/pages/UpdateShelter/UpdateShelter.tsx +++ b/src/pages/UpdateShelter/UpdateShelter.tsx @@ -102,7 +102,7 @@ const UpdateShelter = () => { if (loading) return ; return ( -
+
Date: Sun, 2 Jun 2024 14:20:41 -0300 Subject: [PATCH 19/37] =?UTF-8?q?fix:=20[BUG]=20Filtros=20n=C3=A3o=20persi?= =?UTF-8?q?stem=20ao=20retornar=20de=20um=20abrigo=20espec=C3=ADfico.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: persist filters on return * retirando console.log * Feat: Ajustando váriaveis fixas e aprimorando * Feat: otimizando o useEffets e ajustando a condicional * Fix: excluindo console.log * Fix: add key to kebab-case and removing unnecessary dependencies * Fix: check filterData.priorities is array --- src/pages/Home/Home.tsx | 67 ++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index bc088a7a..f4d83701 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -1,6 +1,6 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { RotateCw } from 'lucide-react'; +import { RotateCw} from 'lucide-react'; import qs from 'qs'; import { BurgerMenu, Footer, Header } from '@/components'; @@ -18,33 +18,37 @@ const initialFilterData: IFilterFormProps = { cities: [], }; +const loadFilterData = (): IFilterFormProps => { + const storedFilterData = JSON.parse(localStorage.getItem('filter-data') || '{}'); + return { ...initialFilterData, ...storedFilterData, ...qs.parse(new URLSearchParams(window.location.search).toString()) }; +}; + +const saveFilterData = (filterData: IFilterFormProps) => { + localStorage.setItem('filter-data', JSON.stringify(filterData)); +}; + const Home = () => { const { data: shelters, loading, refresh } = useShelters({ cache: true }); const [isModalOpen, setOpenModal] = useState(false); const [, setSearchParams] = useSearchParams(); - const [filterData, setFilterData] = useState({ - ...initialFilterData, - ...qs.parse(new URLSearchParams(window.location.search).toString()), - }); + const [filterData, setFilterData] = useState(loadFilterData()); const [, setSearch] = useThrottle( { throttle: 400, callback: () => { const params = new URLSearchParams(qs.stringify(filterData)); - setSearchParams(params); - refresh({ - params: params, - }); + refresh({ params }); }, }, - [] + [filterData] ); const clearSearch = useCallback(() => { setSearch(''); setFilterData(initialFilterData); + localStorage.removeItem('filter-data'); setSearchParams(''); refresh(); }, [refresh, setSearch, setSearchParams]); @@ -70,20 +74,12 @@ const Home = () => { (values: IFilterFormProps) => { setOpenModal(false); setFilterData(values); - - const searchQuery = qs.stringify(factorySearchArgs(values), { - skipNulls: true, - }); - + const searchQuery = qs.stringify(values, { skipNulls: true }); setSearchParams(searchQuery); - - refresh({ - params: { - search: searchQuery, - }, - }); + saveFilterData(values); + refresh({ params: { search: searchQuery } }); }, - [refresh, setSearchParams, factorySearchArgs] + [refresh, setSearchParams] ); const handleFetchMore = useCallback(() => { @@ -93,21 +89,16 @@ const Home = () => { perPage: shelters.perPage, search: qs.stringify(factorySearchArgs(filterData)), }; - - refresh( - { - params: params, - }, - true - ); - }, [ - refresh, - filterData, - shelters.filters, - shelters.page, - shelters.perPage, - factorySearchArgs, - ]); + refresh({ params }, true); + }, [refresh, filterData, shelters.filters, shelters.page, shelters.perPage, factorySearchArgs]); + + useEffect(() => { + if (filterData.search || filterData.cities.length > 0 || filterData.priorities.length > 0 || filterData.shelterStatus.length > 0 || filterData.supplyCategoryIds.length > 0 || filterData.supplyIds.length > 0){ + setSearchParams(qs.stringify(filterData)); + refresh({ params: { search: qs.stringify(filterData) } }); + } + saveFilterData(filterData); + }, [filterData, refresh, setSearchParams]); return (
From 95034e21e01f1811d9f687bd2f2dc2235f1b4a28 Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 15:51:10 -0300 Subject: [PATCH 20/37] Creates route and page component --- .../DonationsHistory/DonationsHistory.tsx | 184 ++++++++++++++++++ .../DonationsHistory/components/Donation.tsx | 96 +++++++++ .../components/DonationsPerDay.tsx | 35 ++++ src/pages/DonationsHistory/index.ts | 15 ++ src/pages/DonationsHistory/types.ts | 39 ++++ src/routes/Routes.tsx | 2 + 6 files changed, 371 insertions(+) create mode 100644 src/pages/DonationsHistory/DonationsHistory.tsx create mode 100644 src/pages/DonationsHistory/components/Donation.tsx create mode 100644 src/pages/DonationsHistory/components/DonationsPerDay.tsx create mode 100644 src/pages/DonationsHistory/index.ts create mode 100644 src/pages/DonationsHistory/types.ts diff --git a/src/pages/DonationsHistory/DonationsHistory.tsx b/src/pages/DonationsHistory/DonationsHistory.tsx new file mode 100644 index 00000000..b5d3fd3d --- /dev/null +++ b/src/pages/DonationsHistory/DonationsHistory.tsx @@ -0,0 +1,184 @@ +import { Header, LoadingScreen } from '@/components'; +import { Button } from '@/components/ui/button'; +import { ChevronLeft } from 'lucide-react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useShelter } from '@/hooks'; +import { IDonations, IDonationsPerDay, ViewOptions } from './types'; +import { useDonations } from '@/hooks/useDonations'; +import { useEffect, useState } from 'react'; +import { DonationsPerDay } from './components/DonationsPerDay'; + +const DonationsHistory = () => { + const navigate = useNavigate(); + const params = useParams(); + const { shelterId = '-1' } = params; + const { data: shelter, loading: shelterLoading } = useShelter(shelterId); + const { data: shelterDonations, loading: donationsLoading } = + useDonations(shelterId); + const [donationsReceivedPerDay, setDonationsReceivedPerDay] = useState< + IDonationsPerDay | {} + >([]); + const [donationsGivenPerDay, setDonationsGivenPerDay] = useState< + IDonationsPerDay | {} + >([]); + + const [viewOption, setViewOption] = useState(ViewOptions.Donated); + + const toggleViewOption = () => { + setViewOption((prevOption) => + prevOption === ViewOptions.Donated + ? ViewOptions.Received + : ViewOptions.Donated + ); + }; //Toggles between donates items and received items + + //Groups donations per day + const donationGroupedByDate = (donations: IDonations): IDonationsPerDay => { + console.log(`donations: `, donations); + return donations.reduce((acc, donation) => { + const date = donation.createdAt.split('T')[0]; + + if (!acc[date]) { + acc[date] = []; + } + acc[date].push(donation); + + return acc; + }, {}); + }; + + // Filters donations into received and given based on shelterId + const filterDonationsByCase = ( + donations: IDonationsPerDay, + shelterId: string + ) => { + const receivedDonations: IDonationsPerDay = {}; + const givenDonations: IDonationsPerDay = {}; + + Object.keys(donations).forEach((date) => { + receivedDonations[date] = donations[date].filter( + (donation) => donation.shelter.id === shelterId + ); + givenDonations[date] = donations[date].filter( + (donation) => donation.shelter.id !== shelterId + ); + }); + + return { receivedDonations, givenDonations }; + }; + + useEffect(() => { + if (!donationsLoading) { + const donationsPerDay = donationGroupedByDate(shelterDonations.results); + const { receivedDonations, givenDonations } = filterDonationsByCase( + donationsPerDay, + shelterId + ); + setDonationsGivenPerDay(givenDonations); + setDonationsReceivedPerDay(receivedDonations); + } + }, [donationsLoading]); + + if (!donationsLoading) { + const dailyDonations = { + donated: donationsGivenPerDay, + received: donationsReceivedPerDay, + }; + // Instantiates a DonationsPerDay container for each day + console.log('givenDonations ', donationsGivenPerDay); + console.log('receivedDonations ', donationsReceivedPerDay); + const segmentedDonationsDisplay = Object.keys( + dailyDonations[viewOption] + ).map((day) => { + console.log( + `dailyDonations[viewOption][day] `, + dailyDonations[viewOption][day] + ); + return ( +
+

{day}

+
+ +
+
+ ); + }); + console.log(`segmentedDonationsDisplay `, segmentedDonationsDisplay); + //Instantiates a DonationsPerDay container for each day + // console.log(`donationsGivenPerDay `, donationsGivenPerDay); + // console.log(`donationsReceivedPerDay `, donationsReceivedPerDay); + // const segmentedDonationsDisplay = dailyDonations[viewOption].map((day) => { + // console.log(`day is `, day); + // return ( + // <> + //

{day}

+ //
+ // + //
+ // + // ); + // }); + // } + // console.log('donationsPerDay ', donationsPerDay); + // const dailyDonatedDonations = donationsPerDay.map((donation)=>{if(donation.shelter.id != shelterId)}) + if (donationsLoading) return ; + return ( +
+
navigate('/')} + > + + + } + /> +
+
+

+ Suas doações +

+
+
+
toggleViewOption()} + > +

+ Doado +

+
+
toggleViewOption()} + > +

+ Recebido +

+
+
+ {segmentedDonationsDisplay} +
+
+ ); + } +}; +export { DonationsHistory }; diff --git a/src/pages/DonationsHistory/components/Donation.tsx b/src/pages/DonationsHistory/components/Donation.tsx new file mode 100644 index 00000000..44488534 --- /dev/null +++ b/src/pages/DonationsHistory/components/Donation.tsx @@ -0,0 +1,96 @@ +/* + * - [ ] Needs to adapt field "Doação para" to include "Doação de" when "Recebido" is selected; + * - [ ] Needs refactoring when oficial data structure comes is; + * + */ + +import { Button } from '@/components/ui/button'; +import { Card } from '@/components/ui/card'; +import { ChevronDown, ChevronUp } from 'lucide-react'; +import { useState } from 'react'; +import { IDonationProps, ViewOptions } from '../types'; +import { Printer, PackageCheck, CircleX } from 'lucide-react'; + +import { Chip } from '@/components'; + +const Donation = ({ viewOption, donation }: IDonationProps) => { + const [opened, setOpened] = useState(false); + const Icon = !opened ? ChevronUp : ChevronDown; + const btnLabel = !opened ? 'Ocultar itens doados' : 'Mostrar itens doados'; + console.log(`donationdonation `, donation); + //Cretes list of all items to be displayed + const listOfItems = donation.items.map((item: string, index) => { + return ( +
  • + {`${item.quantity} ${item.supply.measure} ${item.supply.name}`} +
  • + ); + }); + + const getStatusVariant = ( + status: string + ): 'success' | 'danger' | 'moreInfo' => { + if (status === 'Entregue') { + return 'success'; + } else if (status === 'Pendente') { + return 'danger'; + } else { + return 'moreInfo'; + } + }; + + // Obtém o status da doação + const status = donation.status; + const variant = getStatusVariant(status); + + return ( + +
    + {viewOption == ViewOptions.Received ? 'Doação para' : 'Doação de'} + +
    +
    + {viewOption == ViewOptions.Received + ? donation.donatorName + : donation.shelterName} +
    +
    + Criada às {donation.createdAt.split('T')[0]} +
    +
    + +
    + {opened &&
      {listOfItems}
    } +
    + + + +
    +
    + ); +}; + +export { Donation }; diff --git a/src/pages/DonationsHistory/components/DonationsPerDay.tsx b/src/pages/DonationsHistory/components/DonationsPerDay.tsx new file mode 100644 index 00000000..9a32ca98 --- /dev/null +++ b/src/pages/DonationsHistory/components/DonationsPerDay.tsx @@ -0,0 +1,35 @@ +import { IDonationsPerDayProps } from '../types'; +import { Donation } from './Donation'; + +const DonationsPerDay = ({ donations, viewOption }: IDonationsPerDayProps) => { + console.log(`donations `, donations); + const donationsOfDay = donations.map((donation) => { + donation = { + donationId: donation.id, + donatorName: donation.shelter.name, + donatorId: donation.userId, + shelterId: donation.shelter.id, + shelterName: donation.shelter.name, + status: donation.status, + createdAt: donation.createdAt, + updatedAt: donation.updatedAt || null, + items: donation.donationOrderSupplies, + }; + return ( + + ); + }); + console.log(`viewOption in donationsperDay `, viewOption); + console.log(`donationsOfDay `, donationsOfDay); + return ( +
    +
    +
    {donationsOfDay}
    +
    + ); +}; +export { DonationsPerDay }; diff --git a/src/pages/DonationsHistory/index.ts b/src/pages/DonationsHistory/index.ts new file mode 100644 index 00000000..869b8eb1 --- /dev/null +++ b/src/pages/DonationsHistory/index.ts @@ -0,0 +1,15 @@ +/* QUEBRAS DA TASK EM SUB-TAREFAS: + * [x] (Diogo) Criar Page ListDonations no React Router + * [ ] (Diogo) Checar com outro desenvolvedor se já existem types and interfaces para o carrinho para adotarmos o mesmo > Perguntei na issue do Github + * [x] (Diogo) Criar container (Frame 3495040) + * [ ] (Responsável) Criar componente de display de cards por dia (dentro dele será feito display de todos cards com timestamp de determinada data) (data + accordeon) + * [ ] (Responsável) Criar componente: card individual de doação + * [ ] (Responsável) Criar botões para display dinâmico "Doado" / "Recebido" (Frame 3494905) + * [ ] (Responsável) + * [ ] (Responsável) + * [ ] (Responsável) + */ + +import { DonationsHistory } from './DonationsHistory'; + +export { DonationsHistory }; diff --git a/src/pages/DonationsHistory/types.ts b/src/pages/DonationsHistory/types.ts new file mode 100644 index 00000000..2a765ae0 --- /dev/null +++ b/src/pages/DonationsHistory/types.ts @@ -0,0 +1,39 @@ +import { IDonationsData } from '@/hooks/useDonations/types'; + +export type IDonations = IDonationsData[]; +// export type IDonations = IDonationProps[]; +// export interface IDonationProps { +// // shelterName?: string; +// donationId: string; +// shelterId: string; +// shelterName: string; +// donatorName: string; +// donatorId: string; +// status: 'Pendente' | 'Entregue' | 'Cancelado'; +// createdAt: string; +// updatedAt?: string | null; +// items: string[]; //Check how it was structured in cart +// } + +export interface IDonationsPerDay { + [date: string]: IDonations; +} + +export interface IDonationsInGivenDay { + donations: IDonations; +} + +// export type ViewOption = 'donated' | 'received'; +export enum ViewOptions { + Donated = 'donated', + Received = 'received', +} +export interface IDonationsPerDayProps { + donations: IDonations; + viewOption: ViewOptions; +} + +export interface IDonationProps { + viewOption: ViewOptions; + donation: IDonationProps; +} diff --git a/src/routes/Routes.tsx b/src/routes/Routes.tsx index 6dd56608..35d09fa2 100644 --- a/src/routes/Routes.tsx +++ b/src/routes/Routes.tsx @@ -11,6 +11,7 @@ import { PrivacyPolicy, AboutUs, Supporters, + DonationsHistory, } from '@/pages'; const Routes = () => { @@ -24,6 +25,7 @@ const Routes = () => { path="/abrigo/:shelterId/item/cadastrar" element={} /> + } /> } /> } /> } /> From 1b72ee21b3f766ad645585259d788882c903d66a Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 15:51:27 -0300 Subject: [PATCH 21/37] Create Hook for donations --- src/hooks/useDonations/index.ts | 3 ++ src/hooks/useDonations/types.ts | 64 +++++++++++++++++++++++++ src/hooks/useDonations/useDonations.tsx | 9 ++++ 3 files changed, 76 insertions(+) create mode 100755 src/hooks/useDonations/index.ts create mode 100755 src/hooks/useDonations/types.ts create mode 100755 src/hooks/useDonations/useDonations.tsx diff --git a/src/hooks/useDonations/index.ts b/src/hooks/useDonations/index.ts new file mode 100755 index 00000000..65baa472 --- /dev/null +++ b/src/hooks/useDonations/index.ts @@ -0,0 +1,3 @@ +import { useDonations } from './useDonations'; + +export { useDonations }; diff --git a/src/hooks/useDonations/types.ts b/src/hooks/useDonations/types.ts new file mode 100755 index 00000000..e6df23af --- /dev/null +++ b/src/hooks/useDonations/types.ts @@ -0,0 +1,64 @@ +import { IDonations } from '@/pages/DonationsHistory/types'; +import { exitCode } from 'process'; + +// export interface IUseDonationsData { +// shelterId: string; +// shelterName: string; +// donated: IDonations; +// received: IDonations; +// } + +// const IUseDonationsData = { +// id: string; +// userId: string; +// shelterId: string; +// status: enum([ +// DonationOrderStatus.Canceled, +// DonationOrderStatus.Complete, +// DonationOrderStatus.Pending, +// ]), +// supplies: +// createdAt: string; +// updatedAt: string || null; +// } + +export interface IUseDonationsData { + page: number; + perPage: number; + count: number; + results: IDonationsData[]; +} + +export interface IDonationsData { + id: string; + userId: string; + status: string; + shelter: { + id: string; + name: string; + }; + donationOrderSupplies: { + quantity: number; + supply: { + measure: string; + name: string; + }; + }; + createdAt: string; + updatedAt: string; +} + +// interface IDonationOrderSupplies {} +// const CreateDonationOrderScheme = { +// id: true, +// status: true, +// createdAt: true, +// updatedAt: true, +// }).extend({ +// supplies: array( +// object({ +// id: string; +// quantity: number().min(1), +// }), +// ), +// }); diff --git a/src/hooks/useDonations/useDonations.tsx b/src/hooks/useDonations/useDonations.tsx new file mode 100755 index 00000000..2a378827 --- /dev/null +++ b/src/hooks/useDonations/useDonations.tsx @@ -0,0 +1,9 @@ +import { useFetch } from '../useFetch'; +import { PaginatedQueryPath } from '../usePaginatedQuery/paths'; +import { IUseDonationsData } from './types'; + +const useDonations = (shelterId: string) => { + return useFetch(`${PaginatedQueryPath.DonationOrder}`); +}; + +export { useDonations }; From 073e2069f21ff5f92d18ee4bdd01d0de67e5a11d Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 15:51:55 -0300 Subject: [PATCH 22/37] Adds page to index --- src/pages/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/index.ts b/src/pages/index.ts index 8afdb97c..0c5f8a89 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -8,6 +8,7 @@ import { UpdateShelter } from './UpdateShelter'; import { PrivacyPolicy } from './PrivacyPolicy'; import { AboutUs } from './AboutUs'; import { Supporters } from './Supporters'; +import { DonationsHistory } from './DonationsHistory'; export { SignIn, @@ -20,4 +21,5 @@ export { PrivacyPolicy, AboutUs, Supporters, + DonationsHistory, }; From 03402f5c949164cd0503661ed3f9cab43b7c250c Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 16:15:29 -0300 Subject: [PATCH 23/37] Adds update method to donationOrder service --- src/service/donationOrder/donationOrder.service.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/service/donationOrder/donationOrder.service.ts b/src/service/donationOrder/donationOrder.service.ts index 717fd2c9..aba23f35 100644 --- a/src/service/donationOrder/donationOrder.service.ts +++ b/src/service/donationOrder/donationOrder.service.ts @@ -1,5 +1,6 @@ import { api } from '@/api'; import { + DonateOrderStatus, ICreateDonateResponse, ICreateDonationOrderProps, IDonateOrderItem, @@ -29,6 +30,13 @@ const DonationOrderServices = { ); return data; }, + update: async (id: string, payload: { status: DonateOrderStatus }) => { + const { data } = await api.put>( + `/donation/order/${id}`, + payload + ); + return data; + }, }; export { DonationOrderServices }; From c15fd2d7aa73bf2eec87c4cc3539ed285abb16a8 Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 16:16:02 -0300 Subject: [PATCH 24/37] Adds handleCancel and handleConfirm to donations --- .../DonationsHistory/DonationsHistory.tsx | 23 +---------- .../DonationsHistory/components/Donation.tsx | 38 ++++++++++++++++--- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/pages/DonationsHistory/DonationsHistory.tsx b/src/pages/DonationsHistory/DonationsHistory.tsx index b5d3fd3d..c3d7af02 100644 --- a/src/pages/DonationsHistory/DonationsHistory.tsx +++ b/src/pages/DonationsHistory/DonationsHistory.tsx @@ -109,29 +109,8 @@ const DonationsHistory = () => { ); }); console.log(`segmentedDonationsDisplay `, segmentedDonationsDisplay); - //Instantiates a DonationsPerDay container for each day - // console.log(`donationsGivenPerDay `, donationsGivenPerDay); - // console.log(`donationsReceivedPerDay `, donationsReceivedPerDay); - // const segmentedDonationsDisplay = dailyDonations[viewOption].map((day) => { - // console.log(`day is `, day); - // return ( - // <> - //

    {day}

    - //
    - // - //
    - // - // ); - // }); - // } - // console.log('donationsPerDay ', donationsPerDay); - // const dailyDonatedDonations = donationsPerDay.map((donation)=>{if(donation.shelter.id != shelterId)}) if (donationsLoading) return ; + return (
    { const [opened, setOpened] = useState(false); + const [status, setStatus] = useState(donation.status); const Icon = !opened ? ChevronUp : ChevronDown; const btnLabel = !opened ? 'Ocultar itens doados' : 'Mostrar itens doados'; console.log(`donationdonation `, donation); + //Cretes list of all items to be displayed const listOfItems = donation.items.map((item: string, index) => { return (
  • - {`${item.quantity} ${item.supply.measure} ${item.supply.name}`} + {`${item.quantity} ${ + item.supply.measure == 'Unit' ? 'unidade(s)' : item.supply.measure + } ${item.supply.name}`}
  • ); }); @@ -38,10 +44,22 @@ const Donation = ({ viewOption, donation }: IDonationProps) => { return 'moreInfo'; } }; - // Obtém o status da doação - const status = donation.status; + // let status = donation.status; const variant = getStatusVariant(status); + const handleConfirm = async () => { + let statusUpdate = await DonationOrderServices.update(donation.donationId, { + status: DonateOrderStatus.Complete, + }); + setStatus(DonateOrderStatus.Complete); + }; + const handleCancel = async () => { + let statusUpdate = await DonationOrderServices.update(donation.donationId, { + status: DonateOrderStatus.Canceled, + }); + setStatus(DonateOrderStatus.Canceled); + console.log(`Canceled donation ${donation.id}`); + }; return ( @@ -76,13 +94,23 @@ const Donation = ({ viewOption, donation }: IDonationProps) => { Imprimir doação - - + + + + +
    + {title} +
    + + {description} + + + + + +
    +
    + + ); +}; + +export { ConfirmationDialog }; diff --git a/src/pages/DonationsHistory/components/Donation.tsx b/src/pages/DonationsHistory/components/Donation.tsx index 2e355e6d..439885a6 100644 --- a/src/pages/DonationsHistory/components/Donation.tsx +++ b/src/pages/DonationsHistory/components/Donation.tsx @@ -1,28 +1,43 @@ -/* - * - [ ] Needs to adapt field "Doação para" to include "Doação de" when "Recebido" is selected; - * - [ ] Needs refactoring when oficial data structure comes is; - * - */ - +import * as React from 'react'; +import { useState, useEffect } from 'react'; +import { X, Printer, PackageCheck, CircleX } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { ChevronDown, ChevronUp } from 'lucide-react'; -import { useState } from 'react'; import { IDonationProps, ViewOptions } from '../types'; -import { Printer, PackageCheck, CircleX } from 'lucide-react'; - import { Chip } from '@/components'; import { DonationOrderServices } from '@/service/donationOrder/donationOrder.service'; import { DonateOrderStatus } from '@/service/donationOrder/types'; +import { ConfirmationDialog } from './ConfirmationDialog'; // Adjust the import path according to your file structure const Donation = ({ viewOption, donation }: IDonationProps) => { const [opened, setOpened] = useState(false); const [status, setStatus] = useState(donation.status); + + const getDisplayDate = (status: string): DonateOrderStatus => { + if (status === DonateOrderStatus.Complete) { + return `Entregue no dia ${donation.createdAt.split('T')[0]} às + ${donation.createdAt.split('T')[1].slice(0, -5)}`; + } else if (status === DonateOrderStatus.Pending) { + return `Criado no dia ${donation.createdAt.split('T')[0]} às + ${donation.createdAt.split('T')[1].slice(0, -5)}`; + } else if (status === DonateOrderStatus.Canceled) { + return `Cancelado no dia ${donation.createdAt.split('T')[0]} às + ${donation.createdAt.split('T')[1].slice(0, -5)}`; + } + }; + + const [displayDate, setDisplayDate] = useState(getDisplayDate(status)); + + useEffect(() => { + const displayDate = getDisplayDate(status); + setDisplayDate(displayDate); + }, [status]); + const Icon = !opened ? ChevronUp : ChevronDown; const btnLabel = !opened ? 'Ocultar itens doados' : 'Mostrar itens doados'; - console.log(`donationdonation `, donation); - //Cretes list of all items to be displayed + //Creates list of all items to be displayed const listOfItems = donation.items.map((item: string, index) => { return (
  • @@ -33,28 +48,27 @@ const Donation = ({ viewOption, donation }: IDonationProps) => { ); }); - const getStatusVariant = ( - status: string - ): 'success' | 'danger' | 'moreInfo' => { - if (status === 'Entregue') { - return 'success'; - } else if (status === 'Pendente') { - return 'danger'; + const getStatusVariant = (status: string) => { + if (status === DonateOrderStatus.Complete) { + return { label: 'Entregue', color: '#A9CB9D' }; + } else if (status === DonateOrderStatus.Pending) { + return { label: 'Pendente', color: '#F69F9D' }; } else { - return 'moreInfo'; + return { label: 'Cancelado', color: '#D3D3D3' }; } }; - // Obtém o status da doação - // let status = donation.status; - const variant = getStatusVariant(status); + + const statusVariant = getStatusVariant(status); + const handleConfirm = async () => { - let statusUpdate = await DonationOrderServices.update(donation.donationId, { + await DonationOrderServices.update(donation.donationId, { status: DonateOrderStatus.Complete, }); setStatus(DonateOrderStatus.Complete); }; + const handleCancel = async () => { - let statusUpdate = await DonationOrderServices.update(donation.donationId, { + await DonationOrderServices.update(donation.donationId, { status: DonateOrderStatus.Canceled, }); setStatus(DonateOrderStatus.Canceled); @@ -65,16 +79,17 @@ const Donation = ({ viewOption, donation }: IDonationProps) => {
    {viewOption == ViewOptions.Received ? 'Doação para' : 'Doação de'} - +
    {viewOption == ViewOptions.Received ? donation.donatorName : donation.shelterName}
    -
    - Criada às {donation.createdAt.split('T')[0]} -
    +
    {displayDate}
    - - + {status !== DonateOrderStatus.Complete && + status !== DonateOrderStatus.Canceled && ( + <> + {}} + triggerLabel={ + + Cancelar entrega + + } + Icon={CircleX} + /> + {}} + triggerLabel={ + + Confirmar entrega + + } + Icon={PackageCheck} + /> + + )}
    ); diff --git a/src/pages/DonationsHistory/components/DonationsPerDay.tsx b/src/pages/DonationsHistory/components/DonationsPerDay.tsx index 9a32ca98..7f0de351 100644 --- a/src/pages/DonationsHistory/components/DonationsPerDay.tsx +++ b/src/pages/DonationsHistory/components/DonationsPerDay.tsx @@ -2,7 +2,6 @@ import { IDonationsPerDayProps } from '../types'; import { Donation } from './Donation'; const DonationsPerDay = ({ donations, viewOption }: IDonationsPerDayProps) => { - console.log(`donations `, donations); const donationsOfDay = donations.map((donation) => { donation = { donationId: donation.id, @@ -15,16 +14,16 @@ const DonationsPerDay = ({ donations, viewOption }: IDonationsPerDayProps) => { updatedAt: donation.updatedAt || null, items: donation.donationOrderSupplies, }; + console.log(` Donation: `, donation); return ( ); }); - console.log(`viewOption in donationsperDay `, viewOption); - console.log(`donationsOfDay `, donationsOfDay); + return (
    From bf9da1cfac19bf5628267ba0a70c4ad8eb111f7a Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 17:55:49 -0300 Subject: [PATCH 26/37] Reformats display date according to design Co-authored-by: Diogo de Souza Co-authored-by: Alan Barbosa --- src/pages/DonationsHistory/DonationsHistory.tsx | 12 +++++++++++- src/pages/DonationsHistory/components/Donation.tsx | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/DonationsHistory/DonationsHistory.tsx b/src/pages/DonationsHistory/DonationsHistory.tsx index b32ac403..05ab9454 100644 --- a/src/pages/DonationsHistory/DonationsHistory.tsx +++ b/src/pages/DonationsHistory/DonationsHistory.tsx @@ -32,6 +32,16 @@ const DonationsHistory = () => { ); }; //Toggles between donates items and received items + // Util to format date as per design + const formatDate = (dateString) => { + const date = new Date(dateString); + return new Intl.DateTimeFormat('pt-BR', { + day: '2-digit', + month: 'long', + year: 'numeric', + }).format(date); + }; + //Groups donations per day const donationGroupedByDate = (donations: IDonations): IDonationsPerDay => { return donations.reduce((acc, donation) => { @@ -89,7 +99,7 @@ const DonationsHistory = () => { ).map((day) => { return (
    -

    {day}

    +

    {formatDate(day)}

    { const [opened, setOpened] = useState(false); From baa116fd54fb9056402d9b12d0220fb6238b43b7 Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 18:20:50 -0300 Subject: [PATCH 27/37] Corrects namings of variables and titles according to Figma design Co-authored-by: Diogo de Souza Co-authored-by: Alan Barbosa --- .../DonationsHistory/DonationsHistory.tsx | 2 +- .../DonationsHistory/components/Donation.tsx | 37 +++++++++++-------- .../components/DonationsPerDay.tsx | 2 +- src/pages/DonationsHistory/index.ts | 12 ------ 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/pages/DonationsHistory/DonationsHistory.tsx b/src/pages/DonationsHistory/DonationsHistory.tsx index 05ab9454..51512830 100644 --- a/src/pages/DonationsHistory/DonationsHistory.tsx +++ b/src/pages/DonationsHistory/DonationsHistory.tsx @@ -32,7 +32,7 @@ const DonationsHistory = () => { ); }; //Toggles between donates items and received items - // Util to format date as per design + // Util to format date as per Figma design const formatDate = (dateString) => { const date = new Date(dateString); return new Intl.DateTimeFormat('pt-BR', { diff --git a/src/pages/DonationsHistory/components/Donation.tsx b/src/pages/DonationsHistory/components/Donation.tsx index ff8479b3..40bfdccd 100644 --- a/src/pages/DonationsHistory/components/Donation.tsx +++ b/src/pages/DonationsHistory/components/Donation.tsx @@ -13,6 +13,7 @@ import { ConfirmationDialog } from './ConfirmationDialog'; const Donation = ({ viewOption, donation }: IDonationProps) => { const [opened, setOpened] = useState(false); const [status, setStatus] = useState(donation.status); + const [error, setError] = useState(null); const getDisplayDate = (status: string): DonateOrderStatus => { if (status === DonateOrderStatus.Complete) { @@ -61,20 +62,26 @@ const Donation = ({ viewOption, donation }: IDonationProps) => { const statusVariant = getStatusVariant(status); const handleConfirm = async () => { - await DonationOrderServices.update(donation.donationId, { - status: DonateOrderStatus.Complete, - }); - setStatus(DonateOrderStatus.Complete); + try { + await DonationOrderServices.update(donation.donationId, { + status: DonateOrderStatus.Complete, + }); + setStatus(DonateOrderStatus.Complete); + } catch (err) { + setError('Failed to confirm the delivery. Please try again.'); + } }; const handleCancel = async () => { - await DonationOrderServices.update(donation.donationId, { - status: DonateOrderStatus.Canceled, - }); - setStatus(DonateOrderStatus.Canceled); - console.log(`Canceled donation ${donation.id}`); + try { + await DonationOrderServices.update(donation.donationId, { + status: DonateOrderStatus.Canceled, + }); + setStatus(DonateOrderStatus.Canceled); + } catch (err) { + setError('Failed to cancel the delivery. Please try again.'); + } }; - return (
    @@ -113,8 +120,8 @@ const Donation = ({ viewOption, donation }: IDonationProps) => { status !== DonateOrderStatus.Canceled && ( <> { Icon={CircleX} /> {}} triggerLabel={ diff --git a/src/pages/DonationsHistory/components/DonationsPerDay.tsx b/src/pages/DonationsHistory/components/DonationsPerDay.tsx index 7f0de351..4e694967 100644 --- a/src/pages/DonationsHistory/components/DonationsPerDay.tsx +++ b/src/pages/DonationsHistory/components/DonationsPerDay.tsx @@ -14,7 +14,7 @@ const DonationsPerDay = ({ donations, viewOption }: IDonationsPerDayProps) => { updatedAt: donation.updatedAt || null, items: donation.donationOrderSupplies, }; - console.log(` Donation: `, donation); + return ( Perguntei na issue do Github - * [x] (Diogo) Criar container (Frame 3495040) - * [ ] (Responsável) Criar componente de display de cards por dia (dentro dele será feito display de todos cards com timestamp de determinada data) (data + accordeon) - * [ ] (Responsável) Criar componente: card individual de doação - * [ ] (Responsável) Criar botões para display dinâmico "Doado" / "Recebido" (Frame 3494905) - * [ ] (Responsável) - * [ ] (Responsável) - * [ ] (Responsável) - */ - import { DonationsHistory } from './DonationsHistory'; export { DonationsHistory }; From 409416adcc420b5399486943a7f4412bc0d2dafa Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 18:31:29 -0300 Subject: [PATCH 28/37] Eliminates unused code Co-authored-by: Diogo de Souza Co-authored-by: Alan Barbosa --- src/pages/DonationsHistory/components/ConfirmationDialog.tsx | 2 +- src/pages/DonationsHistory/components/Donation.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/DonationsHistory/components/ConfirmationDialog.tsx b/src/pages/DonationsHistory/components/ConfirmationDialog.tsx index 172cf8b0..0c7da3ef 100644 --- a/src/pages/DonationsHistory/components/ConfirmationDialog.tsx +++ b/src/pages/DonationsHistory/components/ConfirmationDialog.tsx @@ -28,7 +28,7 @@ const ConfirmationDialog: React.FC = ({ confirmLabel, cancelLabel, onConfirm, - onCancel, + triggerLabel, Icon, }) => { diff --git a/src/pages/DonationsHistory/components/Donation.tsx b/src/pages/DonationsHistory/components/Donation.tsx index 40bfdccd..6e910111 100644 --- a/src/pages/DonationsHistory/components/Donation.tsx +++ b/src/pages/DonationsHistory/components/Donation.tsx @@ -1,6 +1,5 @@ -import * as React from 'react'; import { useState, useEffect } from 'react'; -import { X, Printer, PackageCheck, CircleX } from 'lucide-react'; +import { Printer, PackageCheck, CircleX } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { ChevronDown, ChevronUp } from 'lucide-react'; From bc51f3a4c29ea8df927e5c4a8b8c3c0de74ea9b3 Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 18:37:31 -0300 Subject: [PATCH 29/37] Remove unused interfaces Co-authored-by: Diogo de Souza Co-authored-by: Alan Barbosa --- src/hooks/useDonations/types.ts | 39 ----------------------------- src/pages/DonationsHistory/types.ts | 13 ---------- 2 files changed, 52 deletions(-) diff --git a/src/hooks/useDonations/types.ts b/src/hooks/useDonations/types.ts index e6df23af..b1c28fc8 100755 --- a/src/hooks/useDonations/types.ts +++ b/src/hooks/useDonations/types.ts @@ -1,27 +1,3 @@ -import { IDonations } from '@/pages/DonationsHistory/types'; -import { exitCode } from 'process'; - -// export interface IUseDonationsData { -// shelterId: string; -// shelterName: string; -// donated: IDonations; -// received: IDonations; -// } - -// const IUseDonationsData = { -// id: string; -// userId: string; -// shelterId: string; -// status: enum([ -// DonationOrderStatus.Canceled, -// DonationOrderStatus.Complete, -// DonationOrderStatus.Pending, -// ]), -// supplies: -// createdAt: string; -// updatedAt: string || null; -// } - export interface IUseDonationsData { page: number; perPage: number; @@ -47,18 +23,3 @@ export interface IDonationsData { createdAt: string; updatedAt: string; } - -// interface IDonationOrderSupplies {} -// const CreateDonationOrderScheme = { -// id: true, -// status: true, -// createdAt: true, -// updatedAt: true, -// }).extend({ -// supplies: array( -// object({ -// id: string; -// quantity: number().min(1), -// }), -// ), -// }); diff --git a/src/pages/DonationsHistory/types.ts b/src/pages/DonationsHistory/types.ts index 2a765ae0..5c0bb84e 100644 --- a/src/pages/DonationsHistory/types.ts +++ b/src/pages/DonationsHistory/types.ts @@ -1,19 +1,6 @@ import { IDonationsData } from '@/hooks/useDonations/types'; export type IDonations = IDonationsData[]; -// export type IDonations = IDonationProps[]; -// export interface IDonationProps { -// // shelterName?: string; -// donationId: string; -// shelterId: string; -// shelterName: string; -// donatorName: string; -// donatorId: string; -// status: 'Pendente' | 'Entregue' | 'Cancelado'; -// createdAt: string; -// updatedAt?: string | null; -// items: string[]; //Check how it was structured in cart -// } export interface IDonationsPerDay { [date: string]: IDonations; From 4838b8e5a38272811278a1dd978c4994393134eb Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 18:43:07 -0300 Subject: [PATCH 30/37] Closes #327 Co-authored-by: Diogo de Souza Co-authored-by: Alan Barbosa --- src/pages/DonationsHistory/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/DonationsHistory/types.ts b/src/pages/DonationsHistory/types.ts index 5c0bb84e..1229f76a 100644 --- a/src/pages/DonationsHistory/types.ts +++ b/src/pages/DonationsHistory/types.ts @@ -10,7 +10,6 @@ export interface IDonationsInGivenDay { donations: IDonations; } -// export type ViewOption = 'donated' | 'received'; export enum ViewOptions { Donated = 'donated', Received = 'received', From 3e946a7e80788b11a3b52899586f750455923550 Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 18:53:51 -0300 Subject: [PATCH 31/37] =?UTF-8?q?Updates=20cart=20so=20that=20button=20'Ve?= =?UTF-8?q?rificar=20hist=C3=B3rico=20de=20doa=C3=A7=C3=B5es'=20redirects?= =?UTF-8?q?=20to=20donations=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Diogo de Souza Co-authored-by: Alan Barbosa --- .../components/DonationSuccess/DonationSuccess.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx b/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx index 6418d37e..1e2cb63d 100644 --- a/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx +++ b/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx @@ -11,15 +11,21 @@ import { SupplyMeasureMap, cn } from '@/lib/utils'; import { useDonationOrder } from '@/hooks'; import { format } from 'date-fns'; import { Button } from '@/components/ui/button'; +import { useNavigate } from 'react-router-dom'; const DonationSuccess = React.forwardRef( (props, ref) => { const { donationOrderId, className = '', ...rest } = props; const { data: donation, loading } = useDonationOrder(donationOrderId); + const navigate = useNavigate(); if (loading) return ; + const handleRedirect = () => { + navigate(`/abrigo/${donation.shelter.id}/doacoes`); + }; + return (
    @@ -71,7 +77,11 @@ const DonationSuccess = React.forwardRef(
    -
    From afe4689564facc708fab27ce958e202c02ac31ef Mon Sep 17 00:00:00 2001 From: Diogo de Souza Date: Mon, 3 Jun 2024 20:09:52 -0300 Subject: [PATCH 32/37] Removes comments and changes date function to use lib date-fns Co-authored-by: Diogo de Souza Co-authored-by: Alan Barbosa --- .../DonationsHistory/DonationsHistory.tsx | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/pages/DonationsHistory/DonationsHistory.tsx b/src/pages/DonationsHistory/DonationsHistory.tsx index 51512830..a445bcdc 100644 --- a/src/pages/DonationsHistory/DonationsHistory.tsx +++ b/src/pages/DonationsHistory/DonationsHistory.tsx @@ -7,6 +7,8 @@ import { IDonations, IDonationsPerDay, ViewOptions } from './types'; import { useDonations } from '@/hooks/useDonations'; import { useEffect, useState } from 'react'; import { DonationsPerDay } from './components/DonationsPerDay'; +import { format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; const DonationsHistory = () => { const navigate = useNavigate(); @@ -30,19 +32,8 @@ const DonationsHistory = () => { ? ViewOptions.Received : ViewOptions.Donated ); - }; //Toggles between donates items and received items - - // Util to format date as per Figma design - const formatDate = (dateString) => { - const date = new Date(dateString); - return new Intl.DateTimeFormat('pt-BR', { - day: '2-digit', - month: 'long', - year: 'numeric', - }).format(date); }; - //Groups donations per day const donationGroupedByDate = (donations: IDonations): IDonationsPerDay => { return donations.reduce((acc, donation) => { const date = donation.createdAt.split('T')[0]; @@ -56,7 +47,6 @@ const DonationsHistory = () => { }, {}); }; - // Filters donations into received and given based on shelterId const filterDonationsByCase = ( donations: IDonationsPerDay, shelterId: string @@ -93,13 +83,15 @@ const DonationsHistory = () => { donated: donationsGivenPerDay, received: donationsReceivedPerDay, }; - // Instantiates a DonationsPerDay container for each day + const segmentedDonationsDisplay = Object.keys( dailyDonations[viewOption] ).map((day) => { return (
    -

    {formatDate(day)}

    +

    + {format(day, "dd 'de' MMMM yyyy ", { locale: ptBR })} +

    Date: Wed, 5 Jun 2024 12:58:27 -0300 Subject: [PATCH 33/37] feat: add PetsRs link to shelter card (#302) * feat: add PetsRs link to shelter card * Add style to link * refactor: simplify clear shelter name logic --- .../CardAboutShelter/CardAboutShelter.tsx | 31 +++++++++++++++++-- src/components/CardAboutShelter/utils.ts | 6 +++- .../petsRsShelter/petsRsShelter.service.ts | 12 +++++++ src/service/petsRsShelter/types.ts | 18 +++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 src/service/petsRsShelter/petsRsShelter.service.ts create mode 100644 src/service/petsRsShelter/types.ts diff --git a/src/components/CardAboutShelter/CardAboutShelter.tsx b/src/components/CardAboutShelter/CardAboutShelter.tsx index 34525112..6c65543a 100644 --- a/src/components/CardAboutShelter/CardAboutShelter.tsx +++ b/src/components/CardAboutShelter/CardAboutShelter.tsx @@ -7,23 +7,40 @@ import { Smartphone, Building, MapPinned, + Link, } from 'lucide-react'; import { Card } from '../ui/card'; import { ICardAboutShelter } from './types'; import { InfoRow } from './components'; -import { checkAndFormatAddress } from './utils'; +import { checkAndFormatAddress, getShelterNameBeforeSeparator } from './utils'; import { ShelterCategory } from '@/hooks/useShelter/types'; import { Fragment } from 'react/jsx-runtime'; +import { PetsRsShelterServices } from '@/service/petsRsShelter/petsRsShelter.service'; +import { useEffect, useState } from 'react'; const CardAboutShelter = (props: ICardAboutShelter) => { const { shelter } = props; + const [ petsRsShelterUrl, setPetsRsShelterUrl ] = useState(''); const check = (v?: string | number | boolean | null) => { return v !== undefined && v !== null; }; - const formatAddress = checkAndFormatAddress(shelter, false); + const getPetsRsShelterUrl = async (name: string) => { + const cleanShelterName = getShelterNameBeforeSeparator(name); + const data = await PetsRsShelterServices.getByName(cleanShelterName); + const petsRsShelterUrl = data?.id ? `https://petsrs.com.br/abrigo/${data.id}` : 'https://petsrs.com.br/abrigos'; + return petsRsShelterUrl; + }; + + useEffect(() => { + if(shelter.petFriendly) { + getPetsRsShelterUrl(shelter.name).then((url) => setPetsRsShelterUrl(url) ); + } + },[shelter.petFriendly, shelter.name]); + + const formatAddress = checkAndFormatAddress(shelter, false); return (
    Sobre o abrigo
    @@ -89,6 +106,16 @@ const CardAboutShelter = (props: ICardAboutShelter) => { value={check(shelter.pix) ? `${shelter.pix}` : 'Não informado'} clipboardButton={check(shelter.pix)} /> + {(petsRsShelterUrl != '') ? ( + } + label={ + + Confira o abrigo em petsrs.com.br + + } + /> + ) : ''}
    ); diff --git a/src/components/CardAboutShelter/utils.ts b/src/components/CardAboutShelter/utils.ts index 58d00b67..39b013a0 100644 --- a/src/components/CardAboutShelter/utils.ts +++ b/src/components/CardAboutShelter/utils.ts @@ -25,4 +25,8 @@ const checkAndFormatAddress = ( ); }; -export { checkAndFormatAddress }; +function getShelterNameBeforeSeparator(input: string): string { + return input.replace(/[(\-[{].*$/, ''); +} + +export { checkAndFormatAddress, getShelterNameBeforeSeparator }; diff --git a/src/service/petsRsShelter/petsRsShelter.service.ts b/src/service/petsRsShelter/petsRsShelter.service.ts new file mode 100644 index 00000000..c5de3871 --- /dev/null +++ b/src/service/petsRsShelter/petsRsShelter.service.ts @@ -0,0 +1,12 @@ +import axios from 'axios'; + +import { IPetsRsShelter } from './types'; + +const PetsRsShelterServices = { + getByName: async (name: string): Promise => { + const { data } = await axios.get(`https://cms.petsrs.com.br/api/abrigos?filters[Nome][$containsi]=${name}`); + return data?.data[0]; + }, +} + +export { PetsRsShelterServices }; diff --git a/src/service/petsRsShelter/types.ts b/src/service/petsRsShelter/types.ts new file mode 100644 index 00000000..30de3cc0 --- /dev/null +++ b/src/service/petsRsShelter/types.ts @@ -0,0 +1,18 @@ +export interface Attributes { + Nome: string; + Endereco: string; + Longitude: string; + Latitude: string; + createdAt: string; + updatedAt: string; + publishedAt: string; + Telefone: string | null; + nome_responsavel: string | null; + instagramUrl: string | null; + observacao: string | null; +} + +export interface IPetsRsShelter { + id: number; + attributes: Attributes; +} From a84b04fc634de0bf03ecde59d49eff0cf6509d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Fagundes?= Date: Wed, 5 Jun 2024 14:20:08 -0300 Subject: [PATCH 34/37] refactor: donation history page --- package-lock.json | 32 ++ package.json | 1 + src/components/BurgerMenu/BurgerMenu.tsx | 10 +- src/components/DonationCart/DonationCart.tsx | 5 +- .../DonationSuccess/DonationSuccess.tsx | 6 +- src/components/Loader/Loader.tsx | 12 + src/components/Loader/index.ts | 3 + src/components/Loader/types.ts | 4 + src/components/index.ts | 2 + src/components/ui/tabs.tsx | 53 ++++ src/hooks/index.ts | 2 + src/hooks/useDonations/types.ts | 9 +- src/hooks/useDonations/useDonations.tsx | 11 +- src/lib/utils.ts | 17 ++ .../DonationsHistory/DonationsHistory.tsx | 288 +++++++++--------- .../components/ConfirmationDialog.tsx | 86 ------ .../DonationsHistory/components/Donation.tsx | 156 ---------- .../DonationHistoryListItem.tsx | 105 +++++++ .../DonationHistoryListItem/index.ts | 3 + .../DonationHistoryListItem/types.ts | 8 + .../DonationHistoryStatus.tsx | 41 +++ .../components/DonationHistoryStatus/index.ts | 3 + .../components/DonationHistoryStatus/types.ts | 8 + .../components/DonationsPerDay.tsx | 34 --- .../DonationsHistory/components/index.ts | 4 + src/pages/DonationsHistory/types.ts | 26 +- src/pages/Home/components/Filter/Filter.tsx | 15 +- src/pages/Shelter/Shelter.tsx | 1 + .../ShelterCategoryList.tsx | 13 +- .../components/ShelterCategoryList/types.ts | 1 + src/routes/Routes.tsx | 2 +- 31 files changed, 488 insertions(+), 473 deletions(-) create mode 100644 src/components/Loader/Loader.tsx create mode 100644 src/components/Loader/index.ts create mode 100644 src/components/Loader/types.ts create mode 100644 src/components/ui/tabs.tsx delete mode 100644 src/pages/DonationsHistory/components/ConfirmationDialog.tsx delete mode 100644 src/pages/DonationsHistory/components/Donation.tsx create mode 100644 src/pages/DonationsHistory/components/DonationHistoryListItem/DonationHistoryListItem.tsx create mode 100644 src/pages/DonationsHistory/components/DonationHistoryListItem/index.ts create mode 100644 src/pages/DonationsHistory/components/DonationHistoryListItem/types.ts create mode 100644 src/pages/DonationsHistory/components/DonationHistoryStatus/DonationHistoryStatus.tsx create mode 100644 src/pages/DonationsHistory/components/DonationHistoryStatus/index.ts create mode 100644 src/pages/DonationsHistory/components/DonationHistoryStatus/types.ts delete mode 100644 src/pages/DonationsHistory/components/DonationsPerDay.tsx create mode 100644 src/pages/DonationsHistory/components/index.ts diff --git a/package-lock.json b/package-lock.json index 7aa87d0b..3e9020bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", "axios": "^1.6.8", @@ -1937,6 +1938,37 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz", + "integrity": "sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz", diff --git a/package.json b/package.json index 62a1de6d..bd6d7a11 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", "axios": "^1.6.8", diff --git a/src/components/BurgerMenu/BurgerMenu.tsx b/src/components/BurgerMenu/BurgerMenu.tsx index db04d613..2a756df0 100644 --- a/src/components/BurgerMenu/BurgerMenu.tsx +++ b/src/components/BurgerMenu/BurgerMenu.tsx @@ -10,6 +10,7 @@ import { ShieldAlert, X, } from 'lucide-react'; +import clsx from 'clsx'; import { SessionServices } from '@/service'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; @@ -17,9 +18,8 @@ import { BurguerMenuItem } from './components'; import { Separator } from '../ui/separator'; import { SessionContext } from '@/contexts'; import { usePartners } from '@/hooks'; -import { DialogClose } from '@radix-ui/react-dialog'; import { Button } from '../ui/button'; -import { DialogFooter } from '../ui/dialog'; +import { DialogClose, DialogFooter } from '../ui/dialog'; const BurgerMenu = () => { const { session } = useContext(SessionContext); @@ -63,6 +63,12 @@ const BurgerMenu = () => { link="/sobre-nos" icon={} /> + } + className={clsx({ hidden: !session })} + /> { return ( - + {donationOrderId ? ( ) : ( diff --git a/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx b/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx index 1e2cb63d..1d0df144 100644 --- a/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx +++ b/src/components/DonationCart/components/DonationSuccess/DonationSuccess.tsx @@ -22,10 +22,6 @@ const DonationSuccess = React.forwardRef( if (loading) return ; - const handleRedirect = () => { - navigate(`/abrigo/${donation.shelter.id}/doacoes`); - }; - return (
    @@ -80,7 +76,7 @@ const DonationSuccess = React.forwardRef( diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx new file mode 100644 index 00000000..9c9ca74c --- /dev/null +++ b/src/components/Loader/Loader.tsx @@ -0,0 +1,12 @@ +import { Loader2 } from 'lucide-react'; +import { ILoader } from './types'; + +const Loader = (props: ILoader) => { + const { loading, children } = props; + + if (loading) return ; + + return children; +}; + +export { Loader }; diff --git a/src/components/Loader/index.ts b/src/components/Loader/index.ts new file mode 100644 index 00000000..ba8bc1d7 --- /dev/null +++ b/src/components/Loader/index.ts @@ -0,0 +1,3 @@ +import { Loader } from './Loader'; + +export { Loader }; diff --git a/src/components/Loader/types.ts b/src/components/Loader/types.ts new file mode 100644 index 00000000..6fc5e483 --- /dev/null +++ b/src/components/Loader/types.ts @@ -0,0 +1,4 @@ +export interface ILoader { + loading: boolean; + children?: React.ReactNode; +} diff --git a/src/components/index.ts b/src/components/index.ts index fe877999..591ec155 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -17,6 +17,7 @@ import { BurgerMenu } from './BurgerMenu'; import { BackToTop } from './BackToTop'; import { DonationCartIcon } from './DonationCartIcon'; import { DonationCart } from './DonationCart'; +import { Loader } from './Loader'; export { LoadingScreen, @@ -38,4 +39,5 @@ export { BackToTop, DonationCartIcon, DonationCart, + Loader, }; diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 00000000..f57fffdb --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f28c05ed..f2d13836 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -13,6 +13,7 @@ import { useGithubContributors } from './useGithubContributors'; import { useAuthRoles } from './useAuthRoles'; import { useSupporters } from './useSupporters'; import { useDonationOrder } from './useDonationOrder'; +import { useDonations } from './useDonations'; export { useShelters, @@ -30,4 +31,5 @@ export { useAuthRoles, useSupporters, useDonationOrder, + useDonations, }; diff --git a/src/hooks/useDonations/types.ts b/src/hooks/useDonations/types.ts index b1c28fc8..787e522f 100755 --- a/src/hooks/useDonations/types.ts +++ b/src/hooks/useDonations/types.ts @@ -1,3 +1,6 @@ +import { DonateOrderStatus } from '@/service/donationOrder/types'; +import { SupplyMeasure } from '../useShelter/types'; + export interface IUseDonationsData { page: number; perPage: number; @@ -8,7 +11,7 @@ export interface IUseDonationsData { export interface IDonationsData { id: string; userId: string; - status: string; + status: DonateOrderStatus; shelter: { id: string; name: string; @@ -16,10 +19,10 @@ export interface IDonationsData { donationOrderSupplies: { quantity: number; supply: { - measure: string; + measure: SupplyMeasure; name: string; }; - }; + }[]; createdAt: string; updatedAt: string; } diff --git a/src/hooks/useDonations/useDonations.tsx b/src/hooks/useDonations/useDonations.tsx index 2a378827..dba89357 100755 --- a/src/hooks/useDonations/useDonations.tsx +++ b/src/hooks/useDonations/useDonations.tsx @@ -2,8 +2,15 @@ import { useFetch } from '../useFetch'; import { PaginatedQueryPath } from '../usePaginatedQuery/paths'; import { IUseDonationsData } from './types'; -const useDonations = (shelterId: string) => { - return useFetch(`${PaginatedQueryPath.DonationOrder}`); +const useDonations = () => { + return useFetch(`${PaginatedQueryPath.DonationOrder}`, { + initialValue: { + count: 0, + page: 1, + perPage: 20, + results: [], + }, + }); }; export { useDonations }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 42513669..3d161d9e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,9 +1,11 @@ import { ShelterCategory, SupplyMeasure } from '@/hooks/useShelter/types'; import { IUseSheltersDataSupplyData } from '@/hooks/useShelters/types'; +import { ShelterAvailabilityStatus } from '@/pages/Home/components/Filter/types'; import { ShelterTagInfo, ShelterTagType, } from '@/pages/Home/components/ShelterListItem/types'; +import { DonateOrderStatus } from '@/service/donationOrder/types'; import { SupplyPriority } from '@/service/supply/types'; import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; @@ -158,6 +160,19 @@ const SupplyMeasureMap: Record = { Unit: 'un', }; +const ShelterAvailabilityStatusMap: Record = + { + available: 'Abrigo Disponivel', + unavailable: 'Abrigo Indisponivel', + waiting: 'Sem informação de disponibilidade', + }; + +const DonationStatusMap: Record = { + [DonateOrderStatus.Canceled]: 'Cancelado', + [DonateOrderStatus.Pending]: 'Pendente', + [DonateOrderStatus.Complete]: 'Entregue', +}; + export { cn, getAvailabilityProps, @@ -169,4 +184,6 @@ export { normalizedCompare, checkIsNull, SupplyMeasureMap, + ShelterAvailabilityStatusMap, + DonationStatusMap, }; diff --git a/src/pages/DonationsHistory/DonationsHistory.tsx b/src/pages/DonationsHistory/DonationsHistory.tsx index a445bcdc..b7f29e0d 100644 --- a/src/pages/DonationsHistory/DonationsHistory.tsx +++ b/src/pages/DonationsHistory/DonationsHistory.tsx @@ -1,155 +1,155 @@ -import { Header, LoadingScreen } from '@/components'; -import { Button } from '@/components/ui/button'; -import { ChevronLeft } from 'lucide-react'; -import { useNavigate, useParams } from 'react-router-dom'; -import { useShelter } from '@/hooks'; -import { IDonations, IDonationsPerDay, ViewOptions } from './types'; -import { useDonations } from '@/hooks/useDonations'; -import { useEffect, useState } from 'react'; -import { DonationsPerDay } from './components/DonationsPerDay'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; - -const DonationsHistory = () => { - const navigate = useNavigate(); - const params = useParams(); - const { shelterId = '-1' } = params; - const { data: shelter, loading: shelterLoading } = useShelter(shelterId); - const { data: shelterDonations, loading: donationsLoading } = - useDonations(shelterId); - const [donationsReceivedPerDay, setDonationsReceivedPerDay] = useState< - IDonationsPerDay | {} - >([]); - const [donationsGivenPerDay, setDonationsGivenPerDay] = useState< - IDonationsPerDay | {} - >([]); - - const [viewOption, setViewOption] = useState(ViewOptions.Donated); - - const toggleViewOption = () => { - setViewOption((prevOption) => - prevOption === ViewOptions.Donated - ? ViewOptions.Received - : ViewOptions.Donated - ); - }; - - const donationGroupedByDate = (donations: IDonations): IDonationsPerDay => { - return donations.reduce((acc, donation) => { - const date = donation.createdAt.split('T')[0]; - - if (!acc[date]) { - acc[date] = []; - } - acc[date].push(donation); - - return acc; - }, {}); - }; +import { useCallback, useState } from 'react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@radix-ui/react-tabs'; +import { cva } from 'class-variance-authority'; - const filterDonationsByCase = ( - donations: IDonationsPerDay, - shelterId: string - ) => { - const receivedDonations: IDonationsPerDay = {}; - const givenDonations: IDonationsPerDay = {}; - - Object.keys(donations).forEach((date) => { - receivedDonations[date] = donations[date].filter( - (donation) => donation.shelter.id === shelterId - ); - givenDonations[date] = donations[date].filter( - (donation) => donation.shelter.id !== shelterId - ); - }); - - return { receivedDonations, givenDonations }; - }; +import { BurgerMenu, Header, Loader } from '@/components'; +import { useDonations } from '@/hooks/useDonations'; +import { DonationHistoryListItem } from './components'; +import { DonationOrderServices } from '@/service'; +import { DonateOrderStatus } from '@/service/donationOrder/types'; +import { useToast } from '@/components/ui/use-toast'; +import { withAuth } from '@/hocs'; - useEffect(() => { - if (!donationsLoading) { - const donationsPerDay = donationGroupedByDate(shelterDonations.results); - const { receivedDonations, givenDonations } = filterDonationsByCase( - donationsPerDay, - shelterId - ); - setDonationsGivenPerDay(givenDonations); - setDonationsReceivedPerDay(receivedDonations); - } - }, [donationsLoading]); +const DonationsHistoryComponent = () => { + const [selectedTab, setSelectedTab] = useState<'made' | 'received'>('made'); + const { + data: donations, + loading: loadingDonations, + refresh, + } = useDonations(); + const [loading, setLoading] = useState>({}); + const { toast } = useToast(); - if (!donationsLoading) { - const dailyDonations = { - donated: donationsGivenPerDay, - received: donationsReceivedPerDay, - }; + const handleChangeTab = useCallback( + (newStatus: 'made' | 'received') => { + setSelectedTab(newStatus); + refresh({ + params: { + op: newStatus, + }, + }); + }, + [refresh] + ); - const segmentedDonationsDisplay = Object.keys( - dailyDonations[viewOption] - ).map((day) => { - return ( -
    -

    - {format(day, "dd 'de' MMMM yyyy ", { locale: ptBR })} -

    - -
    - ); - }); + const handleUpdateDonationStatus = useCallback( + (shelterId: string, newStatus: DonateOrderStatus) => { + setLoading((prev) => ({ ...prev, [shelterId]: true })); + DonationOrderServices.update(shelterId, { status: newStatus }) + .then(() => { + refresh({ params: { op: selectedTab } }); + }) + .catch((err) => { + toast({ + title: 'Ocorreu um erro ao atualizar o status da doação', + description: `${err?.response?.data?.message ?? err}`, + }); + }) + .finally(() => { + setLoading((prev) => ({ ...prev, [shelterId]: false })); + }); + }, + [refresh, selectedTab, toast] + ); - if (donationsLoading) return ; + const tabsVariants = cva('font-semibold text-lg border-b-2 !text-black', { + variants: { + variant: { + active: 'border-red-500', + default: 'border-transparent', + }, + }, + defaultVariants: { + variant: 'default', + }, + }); - return ( -
    -
    navigate('/')} + return ( +
    +
    } /> +
    +

    Suas Doações

    + + + handleChangeTab('made')} + className={tabsVariants({ + variant: selectedTab === 'made' ? 'active' : 'default', + })} > - - - } - /> -
    -
    -

    - Suas doações -

    -
    -
    -
    toggleViewOption()} + Doado + + handleChangeTab('received')} + className={tabsVariants({ + variant: selectedTab === 'received' ? 'active' : 'default', + })} > -

    - Doado -

    -
    -
    toggleViewOption()} - > -

    - Recebido -

    -
    -
    - {segmentedDonationsDisplay} -
    + Recebido +
    +
    + + + {donations.results.length === 0 ? ( +

    Nenhuma doação encontrada

    + ) : ( + donations.results.map((donation) => ( + + handleUpdateDonationStatus( + donation.id, + DonateOrderStatus.Canceled + ) + } + onConfirm={() => + handleUpdateDonationStatus( + donation.id, + DonateOrderStatus.Complete + ) + } + /> + )) + )} +
    +
    + + + {donations.results.length === 0 ? ( +

    Nenhuma doação encontrada

    + ) : ( + donations.results.map((donation) => ( + + handleUpdateDonationStatus( + donation.id, + DonateOrderStatus.Canceled + ) + } + onConfirm={() => + handleUpdateDonationStatus( + donation.id, + DonateOrderStatus.Complete + ) + } + /> + )) + )} +
    +
    +
    - ); - } +
    + ); }; + +const DonationsHistory = withAuth(DonationsHistoryComponent); + export { DonationsHistory }; diff --git a/src/pages/DonationsHistory/components/ConfirmationDialog.tsx b/src/pages/DonationsHistory/components/ConfirmationDialog.tsx deleted file mode 100644 index 0c7da3ef..00000000 --- a/src/pages/DonationsHistory/components/ConfirmationDialog.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from 'react'; -import { - Dialog, - DialogTrigger, - DialogPortal, - DialogOverlay, - DialogContent, - DialogFooter, - DialogTitle, - DialogDescription, -} from '../../../components/ui/dialog'; -import { Button } from '../../../components/ui/button'; - -interface ConfirmationDialogProps { - title: string; - description: string; - confirmLabel: string; - cancelLabel: string; - onConfirm: () => void; - onCancel: () => void; - triggerLabel: React.ReactNode; - Icon: React.ComponentType>; -} - -const ConfirmationDialog: React.FC = ({ - title, - description, - confirmLabel, - cancelLabel, - onConfirm, - - triggerLabel, - Icon, -}) => { - const [isOpen, setIsOpen] = React.useState(false); - - return ( - - - - - - - -
    - {title} -
    - - {description} - - - - - -
    -
    -
    - ); -}; - -export { ConfirmationDialog }; diff --git a/src/pages/DonationsHistory/components/Donation.tsx b/src/pages/DonationsHistory/components/Donation.tsx deleted file mode 100644 index 6e910111..00000000 --- a/src/pages/DonationsHistory/components/Donation.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Printer, PackageCheck, CircleX } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Card } from '@/components/ui/card'; -import { ChevronDown, ChevronUp } from 'lucide-react'; -import { IDonationProps, ViewOptions } from '../types'; -import { Chip } from '@/components'; -import { DonationOrderServices } from '@/service/donationOrder/donationOrder.service'; -import { DonateOrderStatus } from '@/service/donationOrder/types'; -import { ConfirmationDialog } from './ConfirmationDialog'; - -const Donation = ({ viewOption, donation }: IDonationProps) => { - const [opened, setOpened] = useState(false); - const [status, setStatus] = useState(donation.status); - const [error, setError] = useState(null); - - const getDisplayDate = (status: string): DonateOrderStatus => { - if (status === DonateOrderStatus.Complete) { - return `Entregue no dia ${donation.createdAt.split('T')[0]} às - ${donation.createdAt.split('T')[1].slice(0, -5)}`; - } else if (status === DonateOrderStatus.Pending) { - return `Criado no dia ${donation.createdAt.split('T')[0]} às - ${donation.createdAt.split('T')[1].slice(0, -5)}`; - } else if (status === DonateOrderStatus.Canceled) { - return `Cancelado no dia ${donation.createdAt.split('T')[0]} às - ${donation.createdAt.split('T')[1].slice(0, -5)}`; - } - }; - - const [displayDate, setDisplayDate] = useState(getDisplayDate(status)); - - useEffect(() => { - const displayDate = getDisplayDate(status); - setDisplayDate(displayDate); - }, [status]); - - const Icon = !opened ? ChevronUp : ChevronDown; - const btnLabel = !opened ? 'Ocultar itens doados' : 'Mostrar itens doados'; - - //Creates list of all items to be displayed - const listOfItems = donation.items.map((item: string, index) => { - return ( -
  • - {`${item.quantity} ${ - item.supply.measure == 'Unit' ? 'unidade(s)' : item.supply.measure - } ${item.supply.name}`} -
  • - ); - }); - - const getStatusVariant = (status: string) => { - if (status === DonateOrderStatus.Complete) { - return { label: 'Entregue', color: '#A9CB9D' }; - } else if (status === DonateOrderStatus.Pending) { - return { label: 'Pendente', color: '#F69F9D' }; - } else { - return { label: 'Cancelado', color: '#D3D3D3' }; - } - }; - - const statusVariant = getStatusVariant(status); - - const handleConfirm = async () => { - try { - await DonationOrderServices.update(donation.donationId, { - status: DonateOrderStatus.Complete, - }); - setStatus(DonateOrderStatus.Complete); - } catch (err) { - setError('Failed to confirm the delivery. Please try again.'); - } - }; - - const handleCancel = async () => { - try { - await DonationOrderServices.update(donation.donationId, { - status: DonateOrderStatus.Canceled, - }); - setStatus(DonateOrderStatus.Canceled); - } catch (err) { - setError('Failed to cancel the delivery. Please try again.'); - } - }; - return ( - -
    - {viewOption == ViewOptions.Received ? 'Doação para' : 'Doação de'} - -
    -
    - {viewOption == ViewOptions.Received - ? donation.donatorName - : donation.shelterName} -
    -
    {displayDate}
    -
    - -
    - {opened &&
      {listOfItems}
    } -
    - - {status !== DonateOrderStatus.Complete && - status !== DonateOrderStatus.Canceled && ( - <> - {}} - triggerLabel={ - - Cancelar entrega - - } - Icon={CircleX} - /> - {}} - triggerLabel={ - - Confirmar entrega - - } - Icon={PackageCheck} - /> - - )} -
    -
    - ); -}; - -export { Donation }; diff --git a/src/pages/DonationsHistory/components/DonationHistoryListItem/DonationHistoryListItem.tsx b/src/pages/DonationsHistory/components/DonationHistoryListItem/DonationHistoryListItem.tsx new file mode 100644 index 00000000..375d3e28 --- /dev/null +++ b/src/pages/DonationsHistory/components/DonationHistoryListItem/DonationHistoryListItem.tsx @@ -0,0 +1,105 @@ +import { useMemo, useState } from 'react'; +import { format } from 'date-fns'; +import { + ChevronDown, + ChevronUp, + Circle, + CircleX, + PackageCheck, + Printer, +} from 'lucide-react'; +import clsx from 'clsx'; + +import { IDonationHistoryListItem } from './types'; +import { SupplyMeasureMap } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { DonateOrderStatus } from '@/service/donationOrder/types'; +import { DonationHistoryStatus } from '../DonationHistoryStatus'; + +const DonationHistoryListItem = (props: IDonationHistoryListItem) => { + const { data: donation, onCancel, onConfirm, loading } = props; + const [visible, setVisible] = useState(false); + const accordeonLabel = useMemo( + () => (visible ? 'Ocultar itens doados' : 'Mostrar itens doados'), + [visible] + ); + const AccordeonIcon = useMemo( + () => (visible ? ChevronUp : ChevronDown), + [visible] + ); + + return ( +
    + + + +
    + + Doação para + +

    {donation.shelter.name}

    + + às {format(new Date(donation.createdAt), "HH'h'mm 'de' dd/MM/yy")} + +
    + +
      + {donation.donationOrderSupplies.map((s, idx) => ( +
    • + + {s.quantity} + {SupplyMeasureMap[s.supply.measure]} de {s.supply.name} +
    • + ))} +
    +
    + + +
    +
    + ); +}; + +export { DonationHistoryListItem }; diff --git a/src/pages/DonationsHistory/components/DonationHistoryListItem/index.ts b/src/pages/DonationsHistory/components/DonationHistoryListItem/index.ts new file mode 100644 index 00000000..498912cb --- /dev/null +++ b/src/pages/DonationsHistory/components/DonationHistoryListItem/index.ts @@ -0,0 +1,3 @@ +import { DonationHistoryListItem } from './DonationHistoryListItem'; + +export { DonationHistoryListItem }; diff --git a/src/pages/DonationsHistory/components/DonationHistoryListItem/types.ts b/src/pages/DonationsHistory/components/DonationHistoryListItem/types.ts new file mode 100644 index 00000000..9b2c6927 --- /dev/null +++ b/src/pages/DonationsHistory/components/DonationHistoryListItem/types.ts @@ -0,0 +1,8 @@ +import { IDonationsData } from '@/hooks/useDonations/types'; + +export interface IDonationHistoryListItem { + data: IDonationsData; + onConfirm?: () => void; + onCancel?: () => void; + loading?: boolean; +} diff --git a/src/pages/DonationsHistory/components/DonationHistoryStatus/DonationHistoryStatus.tsx b/src/pages/DonationsHistory/components/DonationHistoryStatus/DonationHistoryStatus.tsx new file mode 100644 index 00000000..1466288e --- /dev/null +++ b/src/pages/DonationsHistory/components/DonationHistoryStatus/DonationHistoryStatus.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { cva } from 'class-variance-authority'; + +import { IDonationHistoryStatus } from './types'; +import { DonationStatusMap, cn } from '@/lib/utils'; +import { DonateOrderStatus } from '@/service/donationOrder/types'; + +const DonationHistoryStatus = React.forwardRef< + HTMLDivElement, + IDonationHistoryStatus +>((props, ref) => { + const { status, className = '', children, chipProps = {}, ...rest } = props; + const { className: chipClassName, ...restChipProps } = chipProps; + + const variants = cva('px-2 py-1 font-medium text-xs rounded-2xl', { + variants: { + variant: { + [DonateOrderStatus.Pending]: 'bg-light-yellow', + [DonateOrderStatus.Canceled]: 'bg-gray-300', + [DonateOrderStatus.Complete]: 'bg-light-green', + }, + }, + defaultVariants: { + variant: status, + }, + }); + + return ( +
    +
    + {DonationStatusMap[status]} +
    + {children} +
    + ); +}); + +export { DonationHistoryStatus }; diff --git a/src/pages/DonationsHistory/components/DonationHistoryStatus/index.ts b/src/pages/DonationsHistory/components/DonationHistoryStatus/index.ts new file mode 100644 index 00000000..ea6f681c --- /dev/null +++ b/src/pages/DonationsHistory/components/DonationHistoryStatus/index.ts @@ -0,0 +1,3 @@ +import { DonationHistoryStatus } from './DonationHistoryStatus'; + +export { DonationHistoryStatus }; diff --git a/src/pages/DonationsHistory/components/DonationHistoryStatus/types.ts b/src/pages/DonationsHistory/components/DonationHistoryStatus/types.ts new file mode 100644 index 00000000..abbd3925 --- /dev/null +++ b/src/pages/DonationsHistory/components/DonationHistoryStatus/types.ts @@ -0,0 +1,8 @@ +import { DonateOrderStatus } from '@/service/donationOrder/types'; +import React from 'react'; + +export interface IDonationHistoryStatus + extends React.ComponentPropsWithoutRef<'div'> { + status: DonateOrderStatus; + chipProps?: React.ComponentPropsWithoutRef<'div'>; +} diff --git a/src/pages/DonationsHistory/components/DonationsPerDay.tsx b/src/pages/DonationsHistory/components/DonationsPerDay.tsx deleted file mode 100644 index 4e694967..00000000 --- a/src/pages/DonationsHistory/components/DonationsPerDay.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { IDonationsPerDayProps } from '../types'; -import { Donation } from './Donation'; - -const DonationsPerDay = ({ donations, viewOption }: IDonationsPerDayProps) => { - const donationsOfDay = donations.map((donation) => { - donation = { - donationId: donation.id, - donatorName: donation.shelter.name, - donatorId: donation.userId, - shelterId: donation.shelter.id, - shelterName: donation.shelter.name, - status: donation.status, - createdAt: donation.createdAt, - updatedAt: donation.updatedAt || null, - items: donation.donationOrderSupplies, - }; - - return ( - - ); - }); - - return ( -
    -
    -
    {donationsOfDay}
    -
    - ); -}; -export { DonationsPerDay }; diff --git a/src/pages/DonationsHistory/components/index.ts b/src/pages/DonationsHistory/components/index.ts new file mode 100644 index 00000000..0e6453de --- /dev/null +++ b/src/pages/DonationsHistory/components/index.ts @@ -0,0 +1,4 @@ +import { DonationHistoryListItem } from './DonationHistoryListItem'; +import { DonationHistoryStatus } from './DonationHistoryStatus'; + +export { DonationHistoryListItem, DonationHistoryStatus }; diff --git a/src/pages/DonationsHistory/types.ts b/src/pages/DonationsHistory/types.ts index 1229f76a..cb0ff5c3 100644 --- a/src/pages/DonationsHistory/types.ts +++ b/src/pages/DonationsHistory/types.ts @@ -1,25 +1 @@ -import { IDonationsData } from '@/hooks/useDonations/types'; - -export type IDonations = IDonationsData[]; - -export interface IDonationsPerDay { - [date: string]: IDonations; -} - -export interface IDonationsInGivenDay { - donations: IDonations; -} - -export enum ViewOptions { - Donated = 'donated', - Received = 'received', -} -export interface IDonationsPerDayProps { - donations: IDonations; - viewOption: ViewOptions; -} - -export interface IDonationProps { - viewOption: ViewOptions; - donation: IDonationProps; -} +export {}; diff --git a/src/pages/Home/components/Filter/Filter.tsx b/src/pages/Home/components/Filter/Filter.tsx index d19ebf1b..b8cfab6e 100644 --- a/src/pages/Home/components/Filter/Filter.tsx +++ b/src/pages/Home/components/Filter/Filter.tsx @@ -21,20 +21,11 @@ import { IFilterProps, ShelterAvailabilityStatus, } from './types'; -import { priorityOptions } from '@/lib/utils'; +import { ShelterAvailabilityStatusMap, priorityOptions } from '@/lib/utils'; import CitiesFilter from './CitiesFilter'; import { IUseSuppliesData } from '@/hooks/useSupplies/types'; import { SupplyPriority } from '@/service/supply/types'; -const ShelterAvailabilityStatusMapped: Record< - ShelterAvailabilityStatus, - string -> = { - available: 'Abrigo Disponivel', - unavailable: 'Abrigo Indisponivel', - waiting: 'Sem informação de disponibilidade', -}; - const priorityOpts = Object.entries(priorityOptions).reduce( (prev, [priority, label]) => priority === `${SupplyPriority.NotNeeded}` @@ -71,7 +62,7 @@ const Filter = (props: IFilterProps) => { })), search: data.search, shelterStatus: data.shelterStatus.map((s) => ({ - label: ShelterAvailabilityStatusMapped[s], + label: ShelterAvailabilityStatusMap[s], value: s, })), supplyCategories: data.supplyCategoryIds.map((id) => ({ @@ -134,7 +125,7 @@ const Filter = (props: IFilterProps) => { checked ? [ ...values.shelterStatus, - { label: ShelterAvailabilityStatusMapped[status], value: status }, + { label: ShelterAvailabilityStatusMap[status], value: status }, ] : values.shelterStatus.filter((s) => s.value !== status) ); diff --git a/src/pages/Shelter/Shelter.tsx b/src/pages/Shelter/Shelter.tsx index 7b41487c..e8e64f66 100644 --- a/src/pages/Shelter/Shelter.tsx +++ b/src/pages/Shelter/Shelter.tsx @@ -211,6 +211,7 @@ const Shelter = () => { quantity: l.quantity, }))} onDonate={handleDonate} + shelterId={shelterId} /> {!isLastElement && } diff --git a/src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx b/src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx index a5ac597a..7a0f14a2 100644 --- a/src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx +++ b/src/pages/Shelter/components/ShelterCategoryList/ShelterCategoryList.tsx @@ -1,12 +1,20 @@ +import { useContext, useMemo } from 'react'; import clsx from 'clsx'; import { SupplyMeasureMap, cn, getSupplyPriorityProps } from '@/lib/utils'; import { ShelterCategoryListProps } from './types'; import { CircleStatus } from '@/components'; import { SupplyPriority } from '@/service/supply/types'; +import { DonationCartContext } from '@/contexts'; +import { IDonationCartItem } from '@/contexts/DonationCartContext/types'; const ShelterCategoryList = (props: ShelterCategoryListProps) => { - const { items, name, onDonate } = props; + const { items, name, onDonate, shelterId } = props; + const { carts } = useContext(DonationCartContext); + const cart: IDonationCartItem[] = useMemo( + () => carts[shelterId] ?? [], + [carts, shelterId] + ); return (
    @@ -36,8 +44,9 @@ const ShelterCategoryList = (props: ShelterCategoryListProps) => { -
      +
        {donation.donationOrderSupplies.map((s, idx) => (
      • {
      • ))}
      -
      +
      -
        +
          {donation.donationOrderSupplies.map((s, idx) => (
        • {
        • ))}
        -
        +
        ); -}; +}); export { DonationHistoryListItem }; diff --git a/src/service/donationOrder/types.ts b/src/service/donationOrder/types.ts index cad3c9b7..c34ab460 100644 --- a/src/service/donationOrder/types.ts +++ b/src/service/donationOrder/types.ts @@ -1,5 +1,3 @@ -import { SupplyMeasure } from '@/hooks/useShelter/types'; - export interface IDonateItem { id: string; quantity: number; @@ -24,28 +22,3 @@ export enum DonateOrderStatus { Canceled = 'Canceled', Complete = 'Complete', } - -export interface IDonateOrderItem { - id: string; - status: DonateOrderStatus; - userId: string; - shelter: IDonateOrderShelter; - donationOrderSupplies: IDonateOrderItemSupply[]; - createdAt: string; - updatedAt?: string | null; -} - -export interface IDonateOrderItemSupply { - quantity: number; - supply: IDonateOrderSupply; -} - -export interface IDonateOrderShelter { - id: string; - name: string; -} - -export interface IDonateOrderSupply { - name: string; - measure: SupplyMeasure; -}