feat: cambia pestaña a Calendarios, persiste colapsado por usuario

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
webui
borja 2 weeks ago
parent 87bd59b780
commit 53c6eb81e6

@ -6,7 +6,7 @@
pathname === '/app' ? 'Tareas' :
pathname.startsWith('/app/groups') ? 'Grupos' :
pathname.startsWith('/app/preferences') ? 'Recordatorios' :
pathname.startsWith('/app/integrations') ? 'Integraciones' :
pathname.startsWith('/app/integrations') ? 'Calendarios' :
'Tareas';
</script>
@ -17,7 +17,7 @@
<a href="/app" class:active={$page.url.pathname === '/app'}>Tareas</a>
<a href="/app/groups" class:active={$page.url.pathname.startsWith('/app/groups')}>Grupos</a>
<a href="/app/preferences" class:active={$page.url.pathname.startsWith('/app/preferences')}>Recordatorios</a>
<a href="/app/integrations" class:active={$page.url.pathname.startsWith('/app/integrations')}>Integraciones</a>
<a href="/app/integrations" class="calendar" class:active={$page.url.pathname.startsWith('/app/integrations')}>Calendarios</a>
</nav>
<form method="POST" action="/api/logout">
<button type="submit" class="logout">Cerrar sesión</button>
@ -51,9 +51,9 @@
<span class="icon"></span>
<span class="label">Recordatorios</span>
</a>
<a href="/app/integrations" class:active={$page.url.pathname.startsWith('/app/integrations')} aria-label="Integraciones">
<a href="/app/integrations" class="calendar" class:active={$page.url.pathname.startsWith('/app/integrations')} aria-label="Calendarios">
<span class="icon">📅</span>
<span class="label">Integraciones</span>
<span class="label">Calendarios</span>
</a>
<form method="POST" action="/api/logout" class="logout-tab" aria-label="Salir">
<button type="submit">
@ -188,6 +188,13 @@
color: var(--color-primary);
font-weight: 600;
}
/* Atenuar la pestaña de Calendarios cuando está inactiva */
.tabbar a.calendar {
opacity: 0.8;
}
.tabbar a.calendar.active {
opacity: 1;
}
.tabbar .icon {
font-size: 18px;
line-height: 1;

@ -86,7 +86,7 @@
<h2 class="section-title">Mis tareas (abiertas)</h2>
{#if data.openTasks.length === 0}
<p>No tienes tareas abiertas.</p>
<p>No tienes tareas asignadas. Crea o reclama una para empezar.</p>
{:else}
<Card>
<ul class="list">
@ -106,7 +106,7 @@
<h2 class="section-title">Sin responsable de mis grupos</h2>
{#if data.unassignedOpen.length === 0}
<p>No hay tareas sin responsable en tus grupos.</p>
<p>No hay tareas sin responsable en tus grupos. Crea una nueva o invita a tus compañeros.</p>
{:else}
{#if data.order === 'group'}
{#each groupByGroup(data.unassignedOpen) as g}

@ -1,6 +1,7 @@
<script lang="ts">
import TaskItem from '$lib/ui/data/TaskItem.svelte';
import Card from '$lib/ui/layout/Card.svelte';
import { onMount } from 'svelte';
type GroupItem = {
id: string;
@ -25,6 +26,64 @@
if (params.unassignedFirst) sp.set('unassignedFirst', 'true');
return sp.toString();
}
const storageKey = `groupsCollapsed:v1:${data.userId ?? 'anon'}`;
let collapsed: Record<string, boolean> = {};
function hasTasks(groupId: string): boolean {
const arr = itemsByGroup[groupId] || [];
return Array.isArray(arr) && arr.length > 0;
}
function defaultCollapsedFor(groupId: string): boolean {
// Por defecto, colapsado si no tiene tareas abiertas
return !hasTasks(groupId);
}
function isOpen(groupId: string): boolean {
const v = collapsed[groupId];
if (typeof v === 'boolean') return !v;
return !defaultCollapsedFor(groupId);
}
function saveCollapsed() {
try {
const currentIds = new Set(groups.map(g => g.id));
const pruned: Record<string, boolean> = {};
for (const id of Object.keys(collapsed)) {
if (currentIds.has(id)) pruned[id] = !!collapsed[id];
}
localStorage.setItem(storageKey, JSON.stringify(pruned));
} catch {}
}
function handleToggle(groupId: string, e: Event) {
const open = (e.currentTarget as HTMLDetailsElement).open;
collapsed = { ...collapsed, [groupId]: !open };
saveCollapsed();
}
onMount(() => {
try {
const raw = localStorage.getItem(storageKey);
const saved = raw ? JSON.parse(raw) : {};
const map: Record<string, boolean> = {};
const currentIds = new Set(groups.map(g => g.id));
for (const g of groups) {
map[g.id] = typeof saved?.[g.id] === 'boolean' ? !!saved[g.id] : defaultCollapsedFor(g.id);
}
// Limpieza de claves obsoletas en storage
const cleaned: Record<string, boolean> = {};
for (const k of Object.keys(saved || {})) {
if (currentIds.has(k)) cleaned[k] = !!saved[k];
}
collapsed = map;
localStorage.setItem(storageKey, JSON.stringify(cleaned || map));
} catch {
// si falla, dejamos los defaults (basados en tareas)
collapsed = {};
}
});
</script>
<svelte:head>
@ -53,7 +112,7 @@
</div>
{#each groups as g}
<details open class="group">
<details class="group" open={isOpen(g.id)} on:toggle={(e) => handleToggle(g.id, e)}>
<summary class="group-header">
<span class="name">{g.name ?? g.id}</span>
<span class="counts">

Loading…
Cancel
Save