refactor: extraer TaskCompleteButton y TaskActions y usar en TaskItem

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
main
brobert 1 month ago
parent 815f060156
commit 415548cdce

@ -1,27 +1,13 @@
<script lang="ts">
import {
compareYmd,
todayYmdUTC,
ymdToDmy,
isToday,
isTomorrow,
} from "$lib/utils/date";
import { success, error as toastError } from "$lib/stores/toasts";
import { tick, createEventDispatcher } from "svelte";
import { fade } from "svelte/transition";
import { normalizeDigits } from "$lib/utils/phone";
import { colorForGroup } from "$lib/utils/groupColor";
import DueOkIcon from "$lib/ui/icons/DueOkIcon.svelte";
import DueSoonIcon from "$lib/ui/icons/DueSoonIcon.svelte";
import DueOverdueIcon from "$lib/ui/icons/DueOverdueIcon.svelte";
import AssigneesIcon from "$lib/ui/icons/AssigneesIcon.svelte";
import ClaimIcon from "$lib/ui/icons/ClaimIcon.svelte";
import UnassignIcon from "$lib/ui/icons/UnassignIcon.svelte";
import EditIcon from "$lib/ui/icons/EditIcon.svelte";
import CalendarEditIcon from "$lib/ui/icons/CalendarEditIcon.svelte";
import CheckCircleSuccessIcon from "$lib/ui/icons/CheckCircleSuccessIcon.svelte";
import TaskDueBadge from "$lib/ui/data/task/TaskDueBadge.svelte";
import TaskAssignees from "$lib/ui/data/task/TaskAssignees.svelte";
import TaskCompleteButton from "$lib/ui/data/task/TaskCompleteButton.svelte";
import TaskActions from "$lib/ui/data/task/TaskActions.svelte";
export let id: number;
export let description: string;
@ -43,10 +29,7 @@
assignees.some(
(a) => normalizeDigits(a) === normalizeDigits(currentUserId),
);
$: 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) : "";
// Derivados de fecha ahora los maneja TaskDueBadge
$: groupLabel = groupName != null ? groupName : "Personal";
$: gc = groupId ? colorForGroup(groupId) : null;
@ -167,13 +150,11 @@
}
}
async function saveDate() {
async function saveDate(value: string | null) {
if (busy) return;
busy = true;
try {
const body = {
due_date: dateValue && dateValue.trim() !== "" ? dateValue.trim() : null,
};
const body = { due_date: value };
const res = await fetch(`/api/tasks/${id}`, {
method: "PATCH",
headers: { "content-type": "application/json" },
@ -184,6 +165,7 @@
success("Fecha actualizada");
dispatch("changed", { id, action: "update_due", patch: { due_date } });
editing = false;
dateValue = due_date ?? "";
} else {
const txt = await res.text();
toastError(txt || "No se pudo actualizar la fecha");
@ -204,8 +186,7 @@
function clearDate() {
if (busy) return;
if (!confirm("¿Quitar la fecha de vencimiento?")) return;
dateValue = "";
saveDate();
saveDate(null);
}
function toggleEditText() {
@ -316,106 +297,35 @@
{/if}
</div>
<div class="complete">
{#if completed}
<button
class="btn primary primary-action"
aria-label="Deshacer completar"
title="Deshacer completar"
on:click|preventDefault={doUncomplete}
disabled={busy}
>
↩️ Deshacer
</button>
{:else}
<button
class="btn primary primary-action"
aria-label="Completar"
title="Completar"
on:click|preventDefault={doComplete}
disabled={busy}
><CheckCircleSuccessIcon />
Completar
</button>
{/if}
<TaskCompleteButton
{completed}
{busy}
on:complete={doComplete}
on:uncomplete={doUncomplete}
/>
</div>
<div class="assignees-container">
<TaskAssignees {id} {assignees} {currentUserId} />
</div>
<div class="actions">
{#if !completed}
{#if !isAssigned}
<button
class="icon-btn secondary-action"
aria-label="Reclamar"
on:click|preventDefault={doClaim}
disabled={busy}
><ClaimIcon />
Reclamar</button
>
{:else}
<button
class="icon-btn secondary-action"
aria-label="Soltar"
title={canUnassign ? "Soltar" : "No puedes soltar una tarea personal. Márcala como completada para eliminarla"}
on:click|preventDefault={doUnassign}
disabled={busy || !canUnassign}
><UnassignIcon />
Soltar</button
>
{/if}
{#if !editingText}
<button
class="icon-btn secondary-action"
aria-label="Editar texto"
title="Editar texto"
on:click|preventDefault={toggleEditText}
disabled={busy}
><EditIcon />
Editar</button
>
{:else}
<button
class="btn primary secondary-action"
on:click|preventDefault={saveText}
disabled={busy}>Guardar</button
>
<button
class="btn ghost secondary-action"
on:click|preventDefault={cancelText}
disabled={busy}>Cancelar</button
>
{/if}
{#if !editing}
<button
class="icon-btn secondary-action"
aria-label="Editar fecha"
title="Editar fecha"
on:click|preventDefault={toggleEdit}
disabled={busy}
><CalendarEditIcon />
Fecha</button
>
{:else}
<input class="date" type="date" bind:value={dateValue} />
<button
class="btn primary secondary-action"
on:click|preventDefault={saveDate}
disabled={busy}>Guardar</button
>
<button
class="btn danger secondary-action"
on:click|preventDefault={clearDate}
disabled={busy}>Quitar</button
>
<button
class="btn ghost secondary-action"
on:click|preventDefault={toggleEdit}
disabled={busy}>Cancelar</button
>
{/if}
{/if}
<TaskActions
{isAssigned}
{canUnassign}
{busy}
{completed}
editingText={editingText}
editingDate={editing}
dateValue={dateValue}
on:claim={doClaim}
on:unassign={doUnassign}
on:toggleEditText={toggleEditText}
on:saveText={saveText}
on:cancelText={cancelText}
on:toggleEditDate={toggleEdit}
on:saveDate={(e) => saveDate((e as CustomEvent<{ value: string | null }>).detail.value)}
on:clearDate={clearDate}
on:cancelDate={() => (editing = false)}
/>
</div>
</li>

@ -0,0 +1,103 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import ClaimIcon from "$lib/ui/icons/ClaimIcon.svelte";
import UnassignIcon from "$lib/ui/icons/UnassignIcon.svelte";
import EditIcon from "$lib/ui/icons/EditIcon.svelte";
import CalendarEditIcon from "$lib/ui/icons/CalendarEditIcon.svelte";
export let isAssigned: boolean;
export let canUnassign: boolean;
export let busy: boolean;
export let completed: boolean;
export let editingText: boolean;
export let editingDate: boolean;
export let dateValue: string = "";
const dispatch = createEventDispatcher<{
claim: void;
unassign: void;
toggleEditText: void;
saveText: void;
cancelText: void;
toggleEditDate: void;
saveDate: { value: string | null };
clearDate: void;
cancelDate: void;
}>();
let localDate = dateValue;
$: if (editingDate) {
localDate = dateValue;
}
</script>
{#if !completed}
{#if !isAssigned}
<button
class="icon-btn secondary-action"
aria-label="Reclamar"
on:click|preventDefault={() => dispatch("claim")}
disabled={busy}
>
<ClaimIcon /> Reclamar
</button>
{:else}
<button
class="icon-btn secondary-action"
aria-label="Soltar"
title={canUnassign ? "Soltar" : "No puedes soltar una tarea personal. Márcala como completada para eliminarla"}
on:click|preventDefault={() => dispatch("unassign")}
disabled={busy || !canUnassign}
>
<UnassignIcon /> Soltar
</button>
{/if}
{#if !editingText}
<button
class="icon-btn secondary-action"
aria-label="Editar texto"
title="Editar texto"
on:click|preventDefault={() => dispatch("toggleEditText")}
disabled={busy}
>
<EditIcon /> Editar
</button>
{:else}
<button class="btn primary secondary-action" on:click|preventDefault={() => dispatch("saveText")} disabled={busy}>
Guardar
</button>
<button class="btn ghost secondary-action" on:click|preventDefault={() => dispatch("cancelText")} disabled={busy}>
Cancelar
</button>
{/if}
{#if !editingDate}
<button
class="icon-btn secondary-action"
aria-label="Editar fecha"
title="Editar fecha"
on:click|preventDefault={() => dispatch("toggleEditDate")}
disabled={busy}
>
<CalendarEditIcon /> Fecha
</button>
{:else}
<input class="date" type="date" bind:value={localDate} />
<button
class="btn primary secondary-action"
on:click|preventDefault={() => dispatch("saveDate", { value: (localDate || "").trim() || null })}
disabled={busy}
>
Guardar
</button>
<button class="btn danger secondary-action" on:click|preventDefault={() => dispatch("clearDate")} disabled={busy}>
Quitar
</button>
<button class="btn ghost secondary-action" on:click|preventDefault={() => dispatch("cancelDate")} disabled={busy}>
Cancelar
</button>
{/if}
{/if}

@ -0,0 +1,35 @@
<script lang="ts">
import CheckCircleSuccessIcon from "$lib/ui/icons/CheckCircleSuccessIcon.svelte";
import { createEventDispatcher } from "svelte";
export let completed: boolean;
export let busy: boolean;
const dispatch = createEventDispatcher<{
complete: void;
uncomplete: void;
}>();
</script>
{#if completed}
<button
class="btn primary primary-action"
aria-label="Deshacer completar"
title="Deshacer completar"
on:click|preventDefault={() => dispatch("uncomplete")}
disabled={busy}
>
↩️ Deshacer
</button>
{:else}
<button
class="btn primary primary-action"
aria-label="Completar"
title="Completar"
on:click|preventDefault={() => dispatch("complete")}
disabled={busy}
>
<CheckCircleSuccessIcon />
Completar
</button>
{/if}
Loading…
Cancel
Save