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

Filtrar solicitudes por aquellas que no tengan un voluntario asignado #180

Merged
merged 7 commits into from
Nov 9, 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
3,178 changes: 3,104 additions & 74 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
"lint": "next lint",
"prettier:check": "prettier . --check",
"prettier:write": "prettier . --write",
"test:e2e": "npx playwright test"
"test:e2e": "npx playwright test",
"supabase:types": "npx supabase gen types --lang=typescript --local > src/types/database.ts",
"snaplet:sync": "npx @snaplet/seed sync",
"snaplet:seed": "npx tsx seed.ts > supabase/automated_seed.sql"
},
"dependencies": {
"@emailjs/browser": "^4.4.1",
Expand All @@ -29,12 +32,16 @@
},
"devDependencies": {
"@playwright/test": "^1.48.2",
"@snaplet/copycat": "^5.1.0",
"@snaplet/seed": "^0.98.0",
"@types/node": "^22.8.7",
"@types/pg": "^8.11.10",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"eslint": "^8",
"eslint-config-next": "15.0.2",
"eslint-config-prettier": "^9.1.0",
"pg": "^8.13.1",
"postcss": "^8",
"prettier": "3.3.3",
"supabase": "^1.215.0",
Expand Down
23 changes: 23 additions & 0 deletions seed.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SeedPg } from '@snaplet/seed/adapter-pg';
import { defineConfig } from '@snaplet/seed/config';
import { Client } from 'pg';

export default defineConfig({
adapter: async () => {
const client = new Client({
connectionString: 'postgresql://postgres:postgres@localhost:54322/postgres',
});
await client.connect();
return new SeedPg(client);
},
select: [
// We don't alter any extensions tables that might be owned by extensions
'!*',
// We want to alter all the tables under public schema
'public*',
// We also want to alter some of the tables under the auth schema
'auth.users',
'auth.identities',
'auth.sessions',
],
});
155 changes: 94 additions & 61 deletions src/app/casos-activos/solicitudes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client';

import { Suspense, useEffect, useState } from 'react';
import { Suspense, useEffect, useState, useCallback, useMemo, ChangeEventHandler } from 'react';
import { supabase } from '@/lib/supabase/client';
import SolicitudCard from '@/components/SolicitudCard';
import Pagination from '@/components/Pagination';
import { useRouter, useSearchParams } from 'next/navigation';
import { tiposAyudaOptions } from '@/helpers/constants';
import { useTowns } from '@/context/TownProvider';
import { HelpRequestData } from '@/types/Requests';
import { Toggle } from '@/components/Toggle';

export const dynamic = 'force-dynamic';

Expand All @@ -19,6 +20,12 @@ export default function SolicitudesPage() {
);
}

const itemsPerPage = 10;
const numPages = (count: number) => {
return Math.ceil(count / itemsPerPage) || 0;
};
const isStringTrue = (str: string): boolean => str === 'true';

function Solicitudes() {
const { towns } = useTowns();
const searchParams = useSearchParams();
Expand All @@ -30,36 +37,45 @@ function Solicitudes() {
const [data, setData] = useState<HelpRequestData[]>([]);
const [currentPage, setCurrentPage] = useState<number>(Number(searchParams.get('page')) || 1);
const [currentCount, setCurrentCount] = useState<number>(0);

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

const updateFilter = (filter: 'urgencia' | 'tipoAyuda' | 'pueblo' | 'page', value: string | number) => {
const params = new URLSearchParams(searchParams.toString());
params.set(filter, value.toString());
router.push(`?${params.toString()}`);
};

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

const changeDataFilter = (type: 'urgencia' | 'tipoAyuda' | 'pueblo', newFilter: string) => {
setFiltroData((prev) => ({
...prev,
[type]: newFilter,
}));
updateFilter(type, newFilter);
};

function changePage(newPage: number) {
setCurrentPage(newPage);
updateFilter('page', newPage);
}
const updateFilter = useCallback(
(filter: 'urgencia' | 'tipoAyuda' | 'pueblo' | 'page' | 'soloSinAsignar', value: string | number) => {
const params = new URLSearchParams(searchParams.toString());
params.set(filter, value.toString());
router.push(`?${params.toString()}`);
},
[searchParams, router],
);

const changeDataFilter = useCallback(
(type: 'urgencia' | 'tipoAyuda' | 'pueblo' | 'soloSinAsignar', newFilter: string) => {
setFiltroData((prev) => ({
...prev,
[type]: newFilter,
}));
updateFilter(type, newFilter);
},
[updateFilter],
);

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

const handleToggleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => changeDataFilter('soloSinAsignar', `${e.target.checked}`),
[],
);

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

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

query.neq('status', 'finished');
// Ejecutar la consulta con paginación
const { data, count, error } = await query
Expand Down Expand Up @@ -128,42 +150,53 @@ function Solicitudes() {
<>
{/* 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={isStringTrue(filtroData.soloSinAsignar)}
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>
);
};
43 changes: 39 additions & 4 deletions src/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { supabase } from './supabase/client';
import { Database } from '@/types/database';
import { HelpRequestAssignmentInsert, HelpRequestUpdate } from '@/types/Requests';
import { createClient } from '@/lib/supabase/server';
import { SupabaseClient } from '@supabase/supabase-js';

export const helpRequestService = {
async createRequest(requestData: Database['public']['Tables']['help_requests']['Insert']) {
Expand Down Expand Up @@ -66,14 +65,50 @@ 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 })
.eq('id', requestData.help_request_id);
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 })
.eq('id', requestId);
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 @@ -193,6 +194,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 @@ -215,6 +217,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: 2 additions & 0 deletions supabase/automated_seed.sql

Large diffs are not rendered by default.

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', 'automated_seed.sql']

[realtime]
enabled = true
Expand Down
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.
Loading