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:
parent
e470e79e78
commit
a313a6e270
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
23
apps/tauri/src/lib/dateFormat.ts
Normal file
23
apps/tauri/src/lib/dateFormat.ts
Normal 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" });
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue