You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
3.0 KiB
Svelte

<script lang="ts">
import Card from '$lib/ui/layout/Card.svelte';
import Badge from '$lib/ui/atoms/Badge.svelte';
import { success, error as toastError } from '$lib/stores/toasts';
export type Counts = { open: number; unassigned: number };
export type TaskPreview = { id: number; description: string; due_date: string | null; display_code: number | null };
export let id: string;
export let name: string | null = null;
export let counts: Counts = { open: 0, unassigned: 0 };
export let previews: TaskPreview[] = [];
let busyTaskId: number | null = null;
async function claim(taskId: number) {
if (busyTaskId) return;
busyTaskId = taskId;
try {
const res = await fetch(`/api/tasks/${taskId}/claim`, { method: 'POST' });
if (res.ok) {
success('Tarea reclamada');
// Actualizar estado local sin recargar
previews = previews.filter((t) => t.id !== taskId);
counts = { ...counts, unassigned: Math.max(0, (counts?.unassigned ?? 0) - 1) };
} else {
const txt = await res.text();
toastError(txt || 'No se pudo reclamar');
}
} catch {
toastError('Error de red');
} finally {
busyTaskId = null;
}
}
</script>
<Card>
<div class="header">
<strong class="name">{name ?? id}</strong>
<div class="badges">
<Badge>abiertas: {counts.open}</Badge>
<Badge tone="warning">sin responsable: {counts.unassigned}</Badge>
</div>
</div>
{#if previews?.length}
<div class="previews">
<em class="title">Sin responsable:</em>
<ul class="list">
{#each previews as t}
<li class="row">
<div class="info">
<span>#{t.display_code ?? t.id}{t.description}</span>
{#if t.due_date}<small class="muted"> (vence: {t.due_date})</small>{/if}
</div>
<div class="actions">
<button class="btn" on:click|preventDefault={() => claim(t.id)} disabled={busyTaskId === t.id}>Reclamar</button>
</div>
</li>
{/each}
</ul>
</div>
{/if}
</Card>
<style>
.header {
display: flex;
align-items: center;
gap: var(--space-2);
justify-content: space-between;
}
.name { font-size: 1rem; }
.badges { display: inline-flex; gap: var(--space-2); flex-wrap: wrap; }
.previews { margin-top: var(--space-3); }
.title { color: var(--color-text); }
.list { margin: 6px 0 0 18px; padding: 0; }
.list li { margin: 4px 0; }
.muted { color: var(--color-text-muted); }
.row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
}
.info {
display: inline-flex;
align-items: baseline;
gap: 6px;
flex-wrap: wrap;
}
.actions {
display: inline-flex;
gap: 6px;
}
.btn {
padding: 3px 8px;
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text);
border-radius: 6px;
font-size: 13px;
cursor: pointer;
}
.btn[disabled] { opacity: .6; cursor: not-allowed; }
</style>