diff --git a/apps/web/src/lib/ui/atoms/Badge.svelte b/apps/web/src/lib/ui/atoms/Badge.svelte new file mode 100644 index 0000000..a405089 --- /dev/null +++ b/apps/web/src/lib/ui/atoms/Badge.svelte @@ -0,0 +1,35 @@ + + + + + diff --git a/apps/web/src/lib/ui/atoms/Button.svelte b/apps/web/src/lib/ui/atoms/Button.svelte new file mode 100644 index 0000000..384a979 --- /dev/null +++ b/apps/web/src/lib/ui/atoms/Button.svelte @@ -0,0 +1,53 @@ + + + + + diff --git a/apps/web/src/lib/ui/atoms/Skeleton.svelte b/apps/web/src/lib/ui/atoms/Skeleton.svelte new file mode 100644 index 0000000..697b046 --- /dev/null +++ b/apps/web/src/lib/ui/atoms/Skeleton.svelte @@ -0,0 +1,24 @@ + + +
+ + diff --git a/apps/web/src/lib/ui/atoms/VisuallyHidden.svelte b/apps/web/src/lib/ui/atoms/VisuallyHidden.svelte new file mode 100644 index 0000000..72a51a9 --- /dev/null +++ b/apps/web/src/lib/ui/atoms/VisuallyHidden.svelte @@ -0,0 +1,11 @@ + + + diff --git a/apps/web/src/lib/ui/data/GroupCard.svelte b/apps/web/src/lib/ui/data/GroupCard.svelte new file mode 100644 index 0000000..26295ce --- /dev/null +++ b/apps/web/src/lib/ui/data/GroupCard.svelte @@ -0,0 +1,52 @@ + + + +
+ {name ?? id} +
+ abiertas: {counts.open} + sin responsable: {counts.unassigned} +
+
+ + {#if previews?.length} +
+ Sin responsable (hasta 3): +
    + {#each previews as t} +
  • + #{t.display_code ?? t.id} — {t.description} + {#if t.due_date} (vence: {t.due_date}){/if} +
  • + {/each} +
+
+ {/if} +
+ + diff --git a/apps/web/src/lib/ui/data/TaskItem.svelte b/apps/web/src/lib/ui/data/TaskItem.svelte new file mode 100644 index 0000000..6f6a1e1 --- /dev/null +++ b/apps/web/src/lib/ui/data/TaskItem.svelte @@ -0,0 +1,60 @@ + + +
  • +
    + #{code} + {description} + {#if due_date} + {#if status === 'overdue'} + vence: {due_date} + {:else if status === 'soon'} + vence: {due_date} + {:else} + vence: {due_date} + {/if} + {/if} +
    + {#if assignees?.length} +
    + asignados: {assignees.join(', ')} +
    + {/if} +
  • + + diff --git a/apps/web/src/lib/ui/inputs/SegmentedControl.svelte b/apps/web/src/lib/ui/inputs/SegmentedControl.svelte new file mode 100644 index 0000000..7e1780f --- /dev/null +++ b/apps/web/src/lib/ui/inputs/SegmentedControl.svelte @@ -0,0 +1,45 @@ + + +
    + {#each options as opt} + + {/each} +
    + + diff --git a/apps/web/src/lib/ui/inputs/TextField.svelte b/apps/web/src/lib/ui/inputs/TextField.svelte new file mode 100644 index 0000000..aadcb58 --- /dev/null +++ b/apps/web/src/lib/ui/inputs/TextField.svelte @@ -0,0 +1,32 @@ + + + + + diff --git a/apps/web/src/lib/ui/layout/AppShell.svelte b/apps/web/src/lib/ui/layout/AppShell.svelte index 4bdb612..a65fb21 100644 --- a/apps/web/src/lib/ui/layout/AppShell.svelte +++ b/apps/web/src/lib/ui/layout/AppShell.svelte @@ -1,13 +1,14 @@
    Tareas
    @@ -31,39 +32,50 @@ .row { display: flex; align-items: center; - gap: var(--space-4); - min-height: 56px; + gap: var(--space-3); + min-height: 52px; } .brand { font-weight: 700; color: var(--color-primary); + text-decoration: none; } .nav { display: flex; - gap: var(--space-3); + gap: var(--space-2); margin-left: auto; } .nav a { - padding: 8px 10px; + padding: 6px 10px; border-radius: var(--radius-sm); + text-decoration: none; + color: inherit; } .nav a:hover, .nav a:focus-visible { background: rgba(0,0,0,0.04); } + .nav a.active { + background: rgba(37, 99, 235, 0.12); + color: var(--color-primary); + font-weight: 600; + } @media (prefers-color-scheme: dark) { .nav a:hover, .nav a:focus-visible { background: rgba(255,255,255,0.06); } + .nav a.active { + background: rgba(96, 165, 250, 0.14); + } } .logout { - margin-left: var(--space-3); + margin-left: var(--space-2); min-height: 36px; padding: 0 10px; } .main { - padding-top: var(--space-5); - padding-bottom: var(--space-5); + padding-top: var(--space-4); + padding-bottom: var(--space-4); } diff --git a/apps/web/src/lib/ui/layout/Card.svelte b/apps/web/src/lib/ui/layout/Card.svelte new file mode 100644 index 0000000..0e7deb7 --- /dev/null +++ b/apps/web/src/lib/ui/layout/Card.svelte @@ -0,0 +1,13 @@ +
    + +
    + + diff --git a/apps/web/src/lib/ui/layout/Pagination.svelte b/apps/web/src/lib/ui/layout/Pagination.svelte new file mode 100644 index 0000000..c1d1aed --- /dev/null +++ b/apps/web/src/lib/ui/layout/Pagination.svelte @@ -0,0 +1,32 @@ + + + + + diff --git a/apps/web/src/lib/utils/date.ts b/apps/web/src/lib/utils/date.ts new file mode 100644 index 0000000..3e8e375 --- /dev/null +++ b/apps/web/src/lib/utils/date.ts @@ -0,0 +1,31 @@ +export function todayYmdUTC(): string { + const d = new Date(); + const y = d.getUTCFullYear(); + const m = String(d.getUTCMonth() + 1).padStart(2, '0'); + const day = String(d.getUTCDate()).padStart(2, '0'); + return `${y}-${m}-${day}`; +} + +export function compareYmd(a: string, b: string): number { + // returns -1 if ab + if (a === b) return 0; + return a < b ? -1 : 1; +} + +export function addDaysYmd(ymd: string, days: number): string { + const d = new Date(`${ymd}T00:00:00Z`); + d.setUTCDate(d.getUTCDate() + days); + const y = d.getUTCFullYear(); + const m = String(d.getUTCMonth() + 1).padStart(2, '0'); + const day = String(d.getUTCDate()).padStart(2, '0'); + return `${y}-${m}-${day}`; +} + +export function dueStatus(ymd: string | null, soonDays: number = 3): 'none' | 'overdue' | 'soon' { + if (!ymd) return 'none'; + const today = todayYmdUTC(); + if (compareYmd(ymd, today) < 0) return 'overdue'; + const soonCut = addDaysYmd(today, soonDays); + if (compareYmd(ymd, soonCut) <= 0) return 'soon'; + return 'none'; +} diff --git a/apps/web/src/routes/app/+page.svelte b/apps/web/src/routes/app/+page.svelte index b35a0e6..8f62f61 100644 --- a/apps/web/src/routes/app/+page.svelte +++ b/apps/web/src/routes/app/+page.svelte @@ -1,4 +1,9 @@ -

    Panel

    -

    Sesión iniciada como: {data.userId}

    - -
    - - - -
    +

    Panel

    +

    Sesión: {data.userId}

    -
    - + +
    + +
    - - - - - -

    - - Diario: cada día a la hora indicada. - Laborables: solo lunes a viernes. - Semanal: los lunes. -

    -
    - -
    - - -

    Zona horaria: {data.tz}

    -
    - - {#if form?.error} -
    {form.error}
    - {/if} - {#if form?.success} -
    Preferencias guardadas.
    - {/if} - - - - -
    -

    Próximo recordatorio

    +
    +

    Preferencias de recordatorios

    + + +
    +
    + + +

    + - Diario: cada día a la hora indicada. - Laborables: solo lunes a viernes. - Semanal: los lunes. +

    +
    + +
    + + +

    Zona horaria: {data.tz}

    +
    + + {#if form?.error} +
    {form.error}
    + {/if} + {#if form?.success} +
    Preferencias guardadas.
    + {/if} + +
    + +
    +
    +
    + +
    +

    Próximo recordatorio

    • Servidor: {data.next ?? '—'}