From 24b29aac184f457ff3b61860797bc6113a779837 Mon Sep 17 00:00:00 2001 From: borja Date: Mon, 13 Oct 2025 17:30:47 +0200 Subject: [PATCH] feat: agregar acciones del servidor para preferencias y simplificar UI Co-authored-by: aider (openrouter/openai/gpt-5) --- .../routes/app/preferences/+page.server.ts | 74 ++++++++++- .../src/routes/app/preferences/+page.svelte | 125 ++---------------- 2 files changed, 80 insertions(+), 119 deletions(-) diff --git a/apps/web/src/routes/app/preferences/+page.server.ts b/apps/web/src/routes/app/preferences/+page.server.ts index ec148ee..9a64e9b 100644 --- a/apps/web/src/routes/app/preferences/+page.server.ts +++ b/apps/web/src/routes/app/preferences/+page.server.ts @@ -1,6 +1,6 @@ -import type { PageServerLoad } from './$types'; +import type { PageServerLoad, Actions } from './$types'; import { getDb } from '$lib/server/db'; -import { redirect } from '@sveltejs/kit'; +import { redirect, fail } from '@sveltejs/kit'; function ymdInTZ(d: Date, tz: string): string { const parts = new Intl.DateTimeFormat('en-GB', { @@ -106,3 +106,73 @@ export const load: PageServerLoad = async ({ locals }) => { next }; }; + +export const actions: Actions = { + default: async ({ locals, request }) => { + const userId = locals.userId ?? null; + if (!userId) { + throw redirect(302, '/login'); + } + + const form = await request.formData(); + const freqRaw = String(form.get('freq') || '').trim().toLowerCase(); + const timeRaw = form.has('time') ? String(form.get('time') || '').trim() : null; + + const allowed = new Set(['off', 'daily', 'weekly', 'weekdays']); + if (!allowed.has(freqRaw)) { + return fail(400, { error: 'freq inválida' }); + } + + function normalizeTime(input: string): string | null { + const m = /^\s*(\d{1,2}):(\d{1,2})\s*$/.exec(input || ''); + if (!m) return null; + const h = Number(m[1]); + const min = Number(m[2]); + if (!Number.isFinite(h) || !Number.isFinite(min)) return null; + if (h < 0 || h > 23 || min < 0 || min > 59) return null; + const hh = String(h).padStart(2, '0'); + const mm = String(min).padStart(2, '0'); + return `${hh}:${mm}`; + } + + const db = await getDb(); + let timeToSave: string | null = null; + + if (freqRaw === 'off') { + if (timeRaw && timeRaw.length > 0) { + const norm = normalizeTime(timeRaw); + if (!norm) return fail(400, { error: 'hora inválida' }); + timeToSave = norm; + } else { + const row = db + .prepare( + `SELECT reminder_time AS time + FROM user_preferences + WHERE user_id = ? + LIMIT 1` + ) + .get(userId) as any; + timeToSave = row?.time ? String(row.time) : '08:30'; + } + } else { + if (!timeRaw || timeRaw.length === 0) { + timeToSave = '08:30'; + } else { + const norm = normalizeTime(timeRaw); + if (!norm) return fail(400, { error: 'hora inválida' }); + timeToSave = norm; + } + } + + db.prepare( + `INSERT INTO user_preferences (user_id, reminder_freq, reminder_time, last_reminded_on, updated_at) + VALUES (?, ?, ?, (SELECT last_reminded_on FROM user_preferences WHERE user_id = ?), strftime('%Y-%m-%d %H:%M:%f','now')) + ON CONFLICT(user_id) DO UPDATE SET + reminder_freq = excluded.reminder_freq, + reminder_time = excluded.reminder_time, + updated_at = excluded.updated_at` + ).run(userId, freqRaw, timeToSave, userId); + + return { success: true, pref: { freq: freqRaw, time: timeToSave } }; + } +}; diff --git a/apps/web/src/routes/app/preferences/+page.svelte b/apps/web/src/routes/app/preferences/+page.svelte index c871259..7f7e57a 100644 --- a/apps/web/src/routes/app/preferences/+page.svelte +++ b/apps/web/src/routes/app/preferences/+page.svelte @@ -4,122 +4,16 @@ tz: string; next: string | null; }; + export let form: any; let freq: 'off' | 'daily' | 'weekly' | 'weekdays' = data.pref.freq; let time: string = data.pref.time ?? '08:30'; - - let saving = false; - let errorMsg: string | null = null; - let successMsg: string | null = null; - let serverNext: string | null = data.next ?? null; - - function normalizeTime(input: string): string | null { - const m = /^\s*(\d{1,2}):(\d{1,2})\s*$/.exec(input || ''); - if (!m) return null; - const h = Number(m[1]); - const min = Number(m[2]); - if (!Number.isFinite(h) || !Number.isFinite(min)) return null; - if (h < 0 || h > 23 || min < 0 || min > 59) return null; - const hh = String(h).padStart(2, '0'); - const mm = String(min).padStart(2, '0'); - return `${hh}:${mm}`; - } - - function ymdInTZ(d: Date, tz: string): string { - const parts = new Intl.DateTimeFormat('en-GB', { - timeZone: tz, - year: 'numeric', - month: '2-digit', - day: '2-digit' - }).formatToParts(d); - const get = (t: string) => parts.find((p) => p.type === t)?.value || ''; - return `${get('year')}-${get('month')}-${get('day')}`; - } - - function hmInTZ(d: Date, tz: string): string { - const parts = new Intl.DateTimeFormat('en-GB', { - timeZone: tz, - hour: '2-digit', - minute: '2-digit', - hour12: false - }).formatToParts(d); - const get = (t: string) => parts.find((p) => p.type === t)?.value || ''; - return `${get('hour')}:${get('minute')}`; - } - - function weekdayShortInTZ(d: Date, tz: string): string { - return new Intl.DateTimeFormat('en-GB', { timeZone: tz, weekday: 'short' }).format(d); - } - - function computeNext(freq: 'off' | 'daily' | 'weekly' | 'weekdays', timeStr: string | null, tz: string): string | null { - if (freq === 'off' || !timeStr) return null; - - const now = new Date(); - const nowHM = hmInTZ(now, tz); - const [nowH, nowM] = String(nowHM).split(':'); - const [cfgH, cfgM] = String(timeStr).split(':'); - const nowMin = (parseInt(nowH || '0', 10) || 0) * 60 + (parseInt(nowM || '0', 10) || 0); - const cfgMin = (parseInt(cfgH || '0', 10) || 0) * 60 + (parseInt(cfgM || '0', 10) || 0); - - const allowDay = (w: string) => { - if (freq === 'daily') return true; - if (freq === 'weekly') return w === 'Mon'; - return w !== 'Sat' && w !== 'Sun'; - }; - - for (let offset = 0; offset < 14; offset++) { - const cand = new Date(now.getTime() + offset * 24 * 60 * 60 * 1000); - const wd = weekdayShortInTZ(cand, tz); - if (!allowDay(wd)) continue; - if (offset === 0 && nowMin >= cfgMin) continue; - - return `${ymdInTZ(cand, tz)} ${normalizeTime(timeStr)}`; - } - return null; - } - - $: clientNext = computeNext(freq, time, data.tz); - - async function onSubmit(e: SubmitEvent) { - e.preventDefault(); - errorMsg = null; - successMsg = null; - saving = true; - try { - const body: any = { freq }; - if (freq !== 'off') { - body.time = time && time.trim() ? time : '08:30'; - } else if (time && time.trim()) { - // Permitimos conservar/actualizar hora estando en off - body.time = time.trim(); - } - const res = await fetch('/api/me/preferences', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(body) - }); - if (res.ok) { - const json = await res.json(); - freq = json.freq; - time = json.time; - serverNext = computeNext(freq, time, data.tz); - successMsg = 'Preferencias guardadas.'; - } else { - const txt = await res.text(); - errorMsg = txt || 'No se pudieron guardar las preferencias.'; - } - } catch (e: any) { - errorMsg = e?.message || 'Error de red al guardar.'; - } finally { - saving = false; - } - }

Preferencias de recordatorios

-
+