refactor: extrae TaskDueBadge y TaskAssignees y actualiza TaskItem
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>main
parent
7a4b21da6f
commit
815f060156
@ -0,0 +1,144 @@
|
||||
<script lang="ts">
|
||||
import { normalizeDigits, buildWaMeUrl } from "$lib/utils/phone";
|
||||
import Popover from "$lib/ui/feedback/Popover.svelte";
|
||||
import AssigneesIcon from "$lib/ui/icons/AssigneesIcon.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
export let id: number;
|
||||
export let assignees: string[] = [];
|
||||
export let currentUserId: string | null | undefined = null;
|
||||
|
||||
let open = false;
|
||||
|
||||
$: assigneesCount = Array.isArray(assignees) ? assignees.length : 0;
|
||||
$: isAssigned =
|
||||
!!currentUserId &&
|
||||
(assignees || []).some((a) => normalizeDigits(a) === normalizeDigits(currentUserId!));
|
||||
$: assigneesAria =
|
||||
assigneesCount === 0
|
||||
? "Sin responsables"
|
||||
: `${assigneesCount} responsable${assigneesCount === 1 ? "" : "s"}${isAssigned ? "; tú incluido" : ""}`;
|
||||
|
||||
onDestroy(() => { open = false; });
|
||||
</script>
|
||||
|
||||
{#if assigneesCount === 0}
|
||||
<button
|
||||
class="assignees-badge empty"
|
||||
type="button"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls={"assignees-popover-" + id}
|
||||
title="Sin responsables"
|
||||
disabled
|
||||
>
|
||||
🙅
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="assignees-badge"
|
||||
class:mine={isAssigned}
|
||||
type="button"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={open}
|
||||
aria-controls={"assignees-popover-" + id}
|
||||
title={assigneesAria}
|
||||
aria-label={assigneesAria}
|
||||
on:click={() => (open = true)}
|
||||
>
|
||||
<span class="icon" aria-hidden="true">
|
||||
<AssigneesIcon />
|
||||
</span>
|
||||
<span class="count">{assigneesCount}</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<Popover bind:open={open} ariaLabel="Responsables" id={"assignees-popover-" + id}>
|
||||
<h3 class="popover-title">Responsables</h3>
|
||||
{#if assigneesCount === 0}
|
||||
<p class="muted">No hay responsables asignados.</p>
|
||||
{:else}
|
||||
<ul class="assignees-list">
|
||||
{#each assignees as a}
|
||||
<li>
|
||||
<a href={buildWaMeUrl(normalizeDigits(a))} target="_blank" rel="noopener noreferrer nofollow">
|
||||
{normalizeDigits(a)}
|
||||
</a>
|
||||
{#if currentUserId && normalizeDigits(a) === normalizeDigits(currentUserId)}
|
||||
<span class="you-pill">tú</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
<div class="popover-actions">
|
||||
<button class="btn ghost" on:click={() => (open = false)}>Cerrar</button>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
.muted { color: var(--color-text-muted); }
|
||||
|
||||
.assignees-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-self: start;
|
||||
gap: 8px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 4px 4px var(--color-border);
|
||||
}
|
||||
.assignees-badge .icon {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.assignees-badge .count {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
.assignees-badge.mine { border-color: var(--color-surface); }
|
||||
.assignees-badge.mine .icon { position: relative; }
|
||||
.assignees-badge.mine .icon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
top: -6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--color-primary);
|
||||
border: 1px solid var(--color-surface);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.assignees-badge[aria-expanded="true"] { border-color: var(--color-primary); }
|
||||
.assignees-badge:disabled { cursor: not-allowed; opacity: 0.6; }
|
||||
.assignees-badge.empty { padding: 2px 6px; gap: 0; }
|
||||
|
||||
.assignees-list { list-style: none; margin: 8px 0; padding: 0; }
|
||||
.assignees-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.assignees-list a { color: var(--color-primary); text-decoration: none; }
|
||||
.assignees-list a:hover, .assignees-list a:focus-visible { text-decoration: underline; }
|
||||
|
||||
.popover-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.popover-title { margin: 0 0 4px 0; font-size: 0.95rem; }
|
||||
.you-pill {
|
||||
margin-left: 6px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 999px;
|
||||
background: rgba(37, 99, 235, 0.12);
|
||||
color: var(--color-primary);
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { compareYmd, todayYmdUTC, ymdToDmy, isToday, isTomorrow } from "$lib/utils/date";
|
||||
import DueOkIcon from "$lib/ui/icons/DueOkIcon.svelte";
|
||||
import DueSoonIcon from "$lib/ui/icons/DueSoonIcon.svelte";
|
||||
import DueOverdueIcon from "$lib/ui/icons/DueOverdueIcon.svelte";
|
||||
|
||||
export let due_date: string | null;
|
||||
|
||||
$: today = todayYmdUTC();
|
||||
$: overdue = !!due_date && compareYmd(due_date, today) < 0;
|
||||
$: imminent = !!due_date && (isToday(due_date!) || isTomorrow(due_date!));
|
||||
$: dateDmy = due_date ? ymdToDmy(due_date) : "";
|
||||
$: title = overdue ? "Vencida" : imminent ? "Próxima" : "Fecha";
|
||||
</script>
|
||||
|
||||
{#if due_date}
|
||||
<span class="date-badge" class:overdue class:soon={imminent} {title}>
|
||||
{#if !overdue && !imminent}
|
||||
<DueOkIcon />
|
||||
{:else if imminent}
|
||||
<DueSoonIcon />
|
||||
{:else}
|
||||
<DueOverdueIcon />
|
||||
{/if}
|
||||
{dateDmy}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.date-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 12px;
|
||||
}
|
||||
.date-badge.overdue {
|
||||
border-color: var(--color-danger);
|
||||
}
|
||||
.date-badge.soon {
|
||||
border-color: var(--color-warning);
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue