refactor: extract shared date formatting utilities in frontend

Three components had duplicate date formatting functions. Extract
formatDateChip (for detail/input views with optional time) and
formatDateLabel (for compact list items) to a shared dateFormat module.

https://claude.ai/code/session_013ooJht2HrZUTXgNJFU79cV
This commit is contained in:
Claude 2026-04-16 07:26:06 +00:00
parent e470e79e78
commit a313a6e270
No known key found for this signature in database
4 changed files with 30 additions and 34 deletions

View file

@ -5,6 +5,7 @@
<script lang="ts"> <script lang="ts">
import { app } from "../stores/app.svelte"; import { app } from "../stores/app.svelte";
import { formatDateChip } from "../dateFormat";
import DateTimePicker from "./DateTimePicker.svelte"; import DateTimePicker from "./DateTimePicker.svelte";
let title = $state(""); let title = $state("");
@ -41,16 +42,6 @@
dateHasTime = hasTime; dateHasTime = hasTime;
} }
function formatDateChip(iso: string): string {
const d = new Date(iso);
const today = new Date();
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const day = dayNames[d.getDay()];
const pad = (n: number) => String(n).padStart(2, "0");
const timePart = dateHasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
}
$effect(() => { $effect(() => {
if (newTaskState.open) { if (newTaskState.open) {
@ -105,7 +96,7 @@
{#if date} {#if date}
<div class="flex items-center gap-1.5 rounded-full border border-border-light bg-black/5 px-3 py-1 text-sm dark:border-border-dark dark:bg-white/10"> <div class="flex items-center gap-1.5 rounded-full border border-border-light bg-black/5 px-3 py-1 text-sm dark:border-border-dark dark:bg-white/10">
<button type="button" onclick={() => (showDatePicker = true)} class="hover:opacity-70"> <button type="button" onclick={() => (showDatePicker = true)} class="hover:opacity-70">
{formatDateChip(date)} {formatDateChip(date, dateHasTime)}
</button> </button>
<button type="button" onclick={() => (date = null)} class="opacity-40 hover:opacity-80"> <button type="button" onclick={() => (date = null)} class="opacity-40 hover:opacity-80">
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">

View file

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Task } from "../types"; import type { Task } from "../types";
import { app } from "../stores/app.svelte"; import { app } from "../stores/app.svelte";
import { formatDateChip } from "../dateFormat";
import DateTimePicker from "./DateTimePicker.svelte"; import DateTimePicker from "./DateTimePicker.svelte";
import ConfirmDialog from "./ConfirmDialog.svelte"; import ConfirmDialog from "./ConfirmDialog.svelte";
import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
@ -118,17 +119,6 @@
} }
}); });
function formatDateChip(iso: string): string {
const d = new Date(iso);
const today = new Date();
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const day = dayNames[d.getDay()];
const pad = (n: number) => String(n).padStart(2, "0");
const hasTime = task.has_time;
const timePart = hasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
}
</script> </script>
<!-- Header --> <!-- Header -->
@ -240,7 +230,7 @@
{#if task.date} {#if task.date}
<div class="flex items-center gap-1.5 rounded-full border border-border-light bg-black/5 px-3 py-1 text-sm dark:border-border-dark dark:bg-white/10"> <div class="flex items-center gap-1.5 rounded-full border border-border-light bg-black/5 px-3 py-1 text-sm dark:border-border-dark dark:bg-white/10">
<button onclick={() => (showDatePicker = true)} class="hover:opacity-70"> <button onclick={() => (showDatePicker = true)} class="hover:opacity-70">
{formatDateChip(task.date)} {formatDateChip(task.date, task.has_time)}
</button> </button>
<button onclick={() => handleDateChange(null)} class="opacity-40 hover:opacity-80"> <button onclick={() => handleDateChange(null)} class="opacity-40 hover:opacity-80">
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">

View file

@ -5,6 +5,7 @@
<script lang="ts"> <script lang="ts">
import type { Task } from "../types"; import type { Task } from "../types";
import { app } from "../stores/app.svelte"; import { app } from "../stores/app.svelte";
import { formatDateLabel } from "../dateFormat";
let { task, onopen, depth = 0, dateChipStyle = "normal", showSubtaskCount = true }: { task: Task; onopen?: (task: Task) => void; depth?: number; dateChipStyle?: "normal" | "overdue" | "hidden"; showSubtaskCount?: boolean } = $props(); let { task, onopen, depth = 0, dateChipStyle = "normal", showSubtaskCount = true }: { task: Task; onopen?: (task: Task) => void; depth?: number; dateChipStyle?: "normal" | "overdue" | "hidden"; showSubtaskCount?: boolean } = $props();
@ -77,15 +78,6 @@
swiping = false; swiping = false;
} }
function formatDate(iso: string): string {
const d = new Date(iso);
const today = new Date();
if (d.toDateString() === today.toDateString()) return "Today";
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
if (d.toDateString() === tomorrow.toDateString()) return "Tomorrow";
return d.toLocaleDateString(undefined, { month: "short", day: "numeric" });
}
</script> </script>
<div <div
@ -153,11 +145,11 @@
{#if task.date && dateChipStyle !== "hidden"} {#if task.date && dateChipStyle !== "hidden"}
{#if dateChipStyle === "overdue"} {#if dateChipStyle === "overdue"}
<span class="mt-1 inline-block rounded-full border border-danger px-2 py-0.5 text-xs text-danger opacity-80"> <span class="mt-1 inline-block rounded-full border border-danger px-2 py-0.5 text-xs text-danger opacity-80">
{formatDate(task.date)} {formatDateLabel(task.date)}
</span> </span>
{:else} {:else}
<span class="mt-1 inline-block rounded-full border border-border-light px-2 py-0.5 text-xs opacity-50 dark:border-border-dark"> <span class="mt-1 inline-block rounded-full border border-border-light px-2 py-0.5 text-xs opacity-50 dark:border-border-dark">
{formatDate(task.date)} {formatDateLabel(task.date)}
</span> </span>
{/if} {/if}
{/if} {/if}

View file

@ -0,0 +1,23 @@
const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const pad = (n: number) => String(n).padStart(2, "0");
/** Format a date for display in chips (detail view, new task input). */
export function formatDateChip(iso: string, hasTime: boolean): string {
const d = new Date(iso);
const today = new Date();
const day = DAY_NAMES[d.getDay()];
const timePart = hasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
}
/** Format a date for compact display in task list items. */
export function formatDateLabel(iso: string): string {
const d = new Date(iso);
const today = new Date();
if (d.toDateString() === today.toDateString()) return "Today";
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
if (d.toDateString() === tomorrow.toDateString()) return "Tomorrow";
return d.toLocaleDateString(undefined, { month: "short", day: "numeric" });
}