diff --git a/apps/web/src/routes/api/me/tasks/+server.ts b/apps/web/src/routes/api/me/tasks/+server.ts new file mode 100644 index 0000000..65f8a06 --- /dev/null +++ b/apps/web/src/routes/api/me/tasks/+server.ts @@ -0,0 +1,115 @@ +import type { RequestHandler } from './$types'; +import { getDb } from '$lib/server/db'; + +function clamp(n: number, min: number, max: number) { + return Math.max(min, Math.min(max, n)); +} + +export const GET: RequestHandler = async (event) => { + // Requiere sesión + const userId = event.locals.userId ?? null; + if (!userId) { + return new Response('Unauthorized', { status: 401 }); + } + + const url = new URL(event.request.url); + const search = (url.searchParams.get('search') || '').trim(); + const status = (url.searchParams.get('status') || 'open').trim().toLowerCase(); + const page = clamp(parseInt(url.searchParams.get('page') || '1', 10) || 1, 1, 100000); + const limit = clamp(parseInt(url.searchParams.get('limit') || '20', 10) || 20, 1, 100); + + // Por ahora solo "open" + if (status !== 'open') { + return new Response('Bad Request', { status: 400 }); + } + + const offset = (page - 1) * limit; + + const db = await getDb(); + + // Construir filtros dinámicos + const whereParts = [ + `a.user_id = ?`, + `COALESCE(t.completed, 0) = 0`, + `t.completed_at IS NULL` + ]; + const params: any[] = [userId]; + + if (search) { + whereParts.push(`t.description LIKE ?`); + params.push(`%${search.replace(/%/g, '\\%').replace(/_/g, '\\_')}%`); + } + + // Total + const totalRow = db + .prepare( + `SELECT COUNT(*) AS cnt + FROM tasks t + INNER JOIN task_assignments a ON a.task_id = t.id + WHERE ${whereParts.join(' AND ')}` + ) + .get(...params) as any; + const total = Number(totalRow?.cnt || 0); + + // Items + const itemsRows = db + .prepare( + `SELECT t.id, t.description, t.due_date, t.group_id, t.display_code + FROM tasks t + INNER JOIN task_assignments a ON a.task_id = t.id + WHERE ${whereParts.join(' AND ')} + ORDER BY + CASE WHEN t.due_date IS NULL THEN 1 ELSE 0 END, + t.due_date ASC, + t.id ASC + LIMIT ? OFFSET ?` + ) + .all(...params, limit, offset) as any[]; + + const items = itemsRows.map((r) => ({ + id: Number(r.id), + description: String(r.description || ''), + due_date: r.due_date ? String(r.due_date) : null, + group_id: r.group_id ? String(r.group_id) : null, + display_code: r.display_code != null ? Number(r.display_code) : null, + assignees: [] as string[] + })); + + // Cargar asignados de todas las tareas recuperadas (si hay) + if (items.length > 0) { + const ids = items.map((it) => it.id); + const placeholders = ids.map(() => '?').join(','); + const assignRows = db + .prepare( + `SELECT task_id, user_id + FROM task_assignments + WHERE task_id IN (${placeholders}) + ORDER BY assigned_at ASC` + ) + .all(...ids) as any[]; + + const map = new Map(); + for (const row of assignRows) { + const tid = Number(row.task_id); + const uid = String(row.user_id); + if (!map.has(tid)) map.set(tid, []); + map.get(tid)!.push(uid); + } + for (const it of items) { + it.assignees = map.get(it.id) || []; + } + } + + const body = { + items, + page, + limit, + total, + hasMore: offset + items.length < total + }; + + return new Response(JSON.stringify(body), { + status: 200, + headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' } + }); +}; diff --git a/apps/web/src/routes/app/+page.server.ts b/apps/web/src/routes/app/+page.server.ts index 57a97a1..be1fc7e 100644 --- a/apps/web/src/routes/app/+page.server.ts +++ b/apps/web/src/routes/app/+page.server.ts @@ -7,5 +7,26 @@ export const load: PageServerLoad = async (event) => { // No hay sesión: redirigir a la home throw redirect(303, '/'); } - return { userId }; + + // Cargar "mis tareas" desde la API interna + let tasks: Array<{ + id: number; + description: string; + due_date: string | null; + group_id: string | null; + display_code: number | null; + assignees: string[]; + }> = []; + + try { + const res = await event.fetch('/api/me/tasks?limit=20'); + if (res.ok) { + const json = await res.json(); + tasks = Array.isArray(json?.items) ? json.items : []; + } + } catch { + // Ignorar errores y dejar lista vacía + } + + return { userId, tasks }; }; diff --git a/apps/web/src/routes/app/+page.svelte b/apps/web/src/routes/app/+page.svelte index 4470e52..e1bfec9 100644 --- a/apps/web/src/routes/app/+page.svelte +++ b/apps/web/src/routes/app/+page.svelte @@ -1,7 +1,48 @@

Panel

Sesión iniciada como: {data.userId}

-

Esta es una página protegida. La cookie de sesión se renueva con cada visita (idle timeout).

+ +
+ +
+ +

Mis tareas (abiertas)

+{#if data.tasks.length === 0} +

No tienes tareas abiertas.

+{:else} + +{/if} + +

La cookie de sesión se renueva con cada visita (idle timeout).