Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Añadir filtro para solicitudes sin voluntarios #156

Merged
merged 10 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 94 additions & 63 deletions src/app/casos-activos/solicitudes/page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { Suspense, useEffect, useState } from 'react';
import { useEffect, useState, useCallback, useMemo, Suspense } from 'react';
import { HeartHandshake } from 'lucide-react';
import { supabase } from '@/lib/supabase/client';
import SolicitudCard from '@/components/SolicitudCard';
Expand All @@ -11,9 +11,15 @@ import { tiposAyudaOptions } from '@/helpers/constants';
import Modal from '@/components/Modal';
import { useModal } from '@/context/ModalProvider';
import { useTowns } from '@/context/TownProvider';
import { Toggle } from '@/components/Toggle';

const MODAL_NAME = 'solicitudes';

const itemsPerPage = 10;
const numPages = (count) => {
return Math.ceil(count / itemsPerPage) || 0;
};

export default function SolicitudesPage() {
return (
<Suspense>
Expand All @@ -35,38 +41,46 @@ function Solicitudes() {
const [currentCount, setCurrentCount] = useState(0);
const { toggleModal } = useModal();

const closeModal = () => {
const closeModal = useCallback(() => {
toggleModal(MODAL_NAME, false);
};
const itemsPerPage = 10;
const numPages = (count) => {
return Math.ceil(count / itemsPerPage) || 0;
};

const updateFilter = (filter, value) => {
const params = new URLSearchParams(searchParams.toString());
params.set(filter, value);
router.push(`?${params.toString()}`);
};
}, [toggleModal]);

const updateFilter = useCallback(
(filter, value) => {
const params = new URLSearchParams(searchParams.toString());
params.set(filter, value);
router.push(`?${params.toString()}`);
},
[searchParams, router],
);

const [filtroData, setFiltroData] = useState({
urgencia: searchParams.get('urgencia') || 'todas',
tipoAyuda: searchParams.get('tipoAyuda') || 'todas',
pueblo: searchParams.get('pueblo') || 'todos',
soloSinVoluntarios: searchParams.get('soloSinVoluntarios') || true,
});

const changeDataFilter = (type, newFilter) => {
setFiltroData((prev) => ({
...prev,
[type]: newFilter,
}));
updateFilter(type, newFilter);
};

function changePage(newPage) {
setCurrentPage(newPage);
updateFilter('page', newPage);
}
const changeDataFilter = useCallback(
(type, newFilter) => {
setFiltroData((prev) => ({
...prev,
[type]: newFilter,
}));
updateFilter(type, newFilter);
},
[updateFilter, setFiltroData],
);

const changePage = useCallback(
(newPage) => {
setCurrentPage(newPage);
updateFilter('page', newPage);
},
[updateFilter],
);

const handleToggleChange = useCallback((e) => changeDataFilter('soloSinVoluntarios', e.target.checked), []);

useEffect(() => {
async function fetchData() {
Expand All @@ -91,6 +105,12 @@ function Solicitudes() {
if (filtroData.urgencia !== 'todas') {
query.eq('urgency', filtroData.urgencia);
}

// Solo agregar filtro si es true
if (!!filtroData.soloSinVoluntarios) {
query.eq('asignees_count', 0);
}

query.neq('status', 'finished');
// Ejecutar la consulta con paginación
const { data, count, error } = await query
Expand All @@ -115,6 +135,8 @@ function Solicitudes() {
fetchData();
}, [filtroData, currentPage]);

const puebloSeleccionado = useMemo(() => getTownById(Number(filtroData.pueblo)), [filtroData, getTownById]);

if (loading) {
return (
<div className="flex justify-center items-center min-h-screen">
Expand All @@ -131,48 +153,57 @@ function Solicitudes() {
);
}

const puebloSeleccionado = getTownById(Number(filtroData.pueblo));

return (
<>
{/* FILTROS */}
<div className="flex flex-col sm:flex-row gap-2 items-center justify-between">
<p className="font-bold text-md">Filtros</p>
<div className="flex flex-col sm:flex-row gap-2 w-full justify-end">
<select
value={filtroData.tipoAyuda}
onChange={(e) => changeDataFilter('tipoAyuda', e.target.value)}
className="px-4 py-2 rounded-lg w-full border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900 shadow-sm"
>
<option value="todas">Todas las necesidades</option>
{Object.entries(tiposAyudaOptions).map(([key, label]) => (
<option key={key} value={key}>
{label}
</option>
))}
</select>
<select
value={filtroData.urgencia}
onChange={(e) => changeDataFilter('urgencia', e.target.value)}
className="px-4 py-2 rounded-lg w-full border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900 shadow-sm"
>
<option value="todas">Todas las prioridades</option>
<option value="alta">Alta prioridad</option>
<option value="media">Media prioridad</option>
<option value="baja">Baja prioridad</option>
</select>
<select
value={filtroData.pueblo}
onChange={(e) => changeDataFilter('pueblo', e.target.value)}
className="px-4 py-2 rounded-lg w-full border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900 shadow-sm"
>
<option value="todos">Todos los pueblos</option>
{towns.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</select>
<div className="flex flex-col space-y-3 w-full">
<div className="flex flex-col sm:flex-row gap-2 w-full justify-end">
<div className="flex flex-col justify-center">
<p className="font-bold text-md">Filtros</p>
</div>
<select
value={filtroData.tipoAyuda}
onChange={(e) => changeDataFilter('tipoAyuda', e.target.value)}
className="px-4 py-2 rounded-lg w-full border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900 shadow-sm"
>
<option value="todas">Todas las necesidades</option>
{Object.entries(tiposAyudaOptions).map(([key, label]) => (
<option key={key} value={key}>
{label}
</option>
))}
</select>
<select
value={filtroData.urgencia}
onChange={(e) => changeDataFilter('urgencia', e.target.value)}
className="px-4 py-2 rounded-lg w-full border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900 shadow-sm"
>
<option value="todas">Todas las prioridades</option>
<option value="alta">Alta prioridad</option>
<option value="media">Media prioridad</option>
<option value="baja">Baja prioridad</option>
</select>
<select
value={filtroData.pueblo}
onChange={(e) => changeDataFilter('pueblo', e.target.value)}
className="px-4 py-2 rounded-lg w-full border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900 shadow-sm"
>
<option value="todos">Todos los pueblos</option>
{towns.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</select>
</div>
<div className="flex flex-row flex-1 justify-end">
<Toggle
handleChange={handleToggleChange}
checked={filtroData.soloSinVoluntarios}
label="Sólo ofertas sin voluntarios"
/>
</div>
</div>
</div>
<div className="grid gap-4">
Expand Down
33 changes: 33 additions & 0 deletions src/components/Toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

type ToggleProps = { handleChange: React.ChangeEventHandler<HTMLInputElement>; checked: boolean; label: string };

export const Toggle = ({ checked, handleChange, label }: ToggleProps) => {
return (
<div className="flex items-center space-x-2">
<label htmlFor="toggle" className="font-medium">
{label}
</label>
<div
className={`relative inline-block w-10 h-6 rounded-full transition-colors duration-300 ${
checked ? 'bg-green-500' : 'bg-gray-300'
}`}
>
<input
type="checkbox"
id="toggle"
className="absolute w-0 h-0 opacity-0"
checked={checked}
onChange={handleChange}
/>
<label htmlFor="toggle">
<span
className={`absolute top-0.5 left-0.5 w-5 h-5 rounded-full bg-white transition-transform duration-300 transform ${
checked ? 'translate-x-4' : ''
}`}
></span>
</label>
</div>
</div>
);
};
40 changes: 37 additions & 3 deletions src/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,48 @@ export const helpRequestService = {

async assign(requestData: HelpRequestAssignmentInsert) {
const { data, error } = await supabase.from('help_request_assignments').insert([requestData]).select();

if (error) throw error;

const { data: linkedRequestData, error: errorGettingLinkedData } = await supabase
.from('help_requests')
.select('*')
.eq('id', requestData.help_request_id);
if (errorGettingLinkedData) throw errorGettingLinkedData;
if (!linkedRequestData) throw new Error('No se puede encontrar esta tarea');

const { error: errorUpdatingAssigneesCount } = await supabase
.from('help_requests')
.update({ asignees_count: linkedRequestData[0].asignees_count + 1 });
if (errorUpdatingAssigneesCount) throw errorUpdatingAssigneesCount;

return data[0];
},
async unassign(id: number) {
const { error } = await supabase.from('help_request_assignments').delete().eq('id', id);
const { data, error: errorFindingRow } = await supabase.from('help_request_assignments').select('*').eq('id', id);
if (errorFindingRow || !data) {
throw new Error('No se puede encontrar la tarea');
}

if (error) throw error;
const requestId = data[0].help_request_id;

const { error: errorDeletingAssignment } = await supabase.from('help_request_assignments').delete().eq('id', id);
if (errorDeletingAssignment) throw errorDeletingAssignment;

const { data: linkedRequestData, error: errorGettingLinkedData } = await supabase
.from('help_requests')
.select('*')
.eq('id', requestId);

if (errorGettingLinkedData) throw errorGettingLinkedData;
if (!linkedRequestData) throw new Error('No se puede encontrar esta tarea');

const { asignees_count } = linkedRequestData[0];
const newNumberAssignees = asignees_count <= 0 ? 0 : asignees_count - 1;

const { error: errorUpdatingAssigneesCount } = await supabase
.from('help_requests')
.update({ asignees_count: newNumberAssignees });
if (errorUpdatingAssigneesCount) throw errorUpdatingAssigneesCount;
},

async getByType(type: any) {
Expand Down
3 changes: 3 additions & 0 deletions src/types/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export type Database = {
help_requests: {
Row: {
additional_info: Json | null;
asignees_count: number;
contact_info: string | null;
coordinates: unknown | null;
created_at: string | null;
Expand All @@ -192,6 +193,7 @@ export type Database = {
};
Insert: {
additional_info?: Json | null;
asignees_count?: number;
contact_info?: string | null;
coordinates?: unknown | null;
created_at?: string | null;
Expand All @@ -213,6 +215,7 @@ export type Database = {
};
Update: {
additional_info?: Json | null;
asignees_count?: number;
contact_info?: string | null;
coordinates?: unknown | null;
created_at?: string | null;
Expand Down
2 changes: 1 addition & 1 deletion supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enabled = true
# Specifies an ordered list of seed files to load during db reset.
# Supports glob patterns relative to supabase directory. For example:
# sql_paths = ['./seeds/*.sql', '../project-src/seeds/*-load-testing.sql']
sql_paths = ['./seed.sql']
sql_paths = ['./towns.sql', './help_requests.sql']

[realtime]
enabled = true
Expand Down
11 changes: 11 additions & 0 deletions supabase/help_requests.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
INSERT INTO "public"."help_requests" ("id","created_at","type","name","location","description","urgency","number_of_people","contact_info","additional_info","status","resources","latitude","longitude","help_type","people_needed","town_id","other_help")
VALUES ('10851', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Lorem Ipsum', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10852', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Lorem Ipsum', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10853', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Lorem Ipsum', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10854', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Me traslado desde r con l', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10855', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Me traslado desde r con l', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10856', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Me traslado desde r con l', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10857', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Me traslado desde r con l', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10858', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Me traslado desde r con l', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10859', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Me traslado desde r con l', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null),
('10860', '2024-11-07 18:31:47.83578+00', 'necesita', 'Rocío', '46200, P', 'Me traslado desde r con l', 'alta', '1', '123456789', '{"email": "[email protected]", "consent": true, "special_situations": null}', 'active', null, '39.1', '-0.4', '{"alojamiento"}', '1', '17', null);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table "public"."help_requests" add column "asignees_count" smallint not null default '0'::smallint;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
UPDATE "public"."help_requests"
SET "asignees_count" = (
SELECT COUNT(*)
FROM "public"."help_request_assignments"
WHERE "public"."help_request_assignments"."help_request_id" = "public"."help_requests"."id"
);
File renamed without changes.