fix: use has_time flag for due date time tracking
Replace the hours==0 && minutes==0 heuristic with an explicit has_time bool field on Task. Existing files without the field deserialize as false (date-only), preserving current behavior. Frontend components pass and receive has_time through DateTimePicker's onchange callback.
This commit is contained in:
parent
970ed9aa1d
commit
72475a552a
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { value = null, onchange, onclose }: {
|
let { value = null, has_time = false, onchange, onclose }: {
|
||||||
value: string | null;
|
value: string | null;
|
||||||
onchange: (iso: string | null) => void;
|
has_time: boolean;
|
||||||
|
onchange: (iso: string | null, has_time: boolean) => void;
|
||||||
onclose: () => void;
|
onclose: () => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
let viewYear = $state(existing ? existing.getFullYear() : now.getFullYear());
|
let viewYear = $state(existing ? existing.getFullYear() : now.getFullYear());
|
||||||
let viewMonth = $state(existing ? existing.getMonth() : now.getMonth());
|
let viewMonth = $state(existing ? existing.getMonth() : now.getMonth());
|
||||||
let selectedDay = $state(existing ? existing.getDate() : now.getDate());
|
let selectedDay = $state(existing ? existing.getDate() : now.getDate());
|
||||||
let includeTime = $state(existing ? (existing.getHours() !== 0 || existing.getMinutes() !== 0) : false);
|
let includeTime = $state(has_time);
|
||||||
let selectedHour = $state(existing ? existing.getHours() : now.getHours());
|
let selectedHour = $state(existing ? existing.getHours() : now.getHours());
|
||||||
let selectedMinute = $state(existing ? existing.getMinutes() : 0);
|
let selectedMinute = $state(existing ? existing.getMinutes() : 0);
|
||||||
let visible = $state(false);
|
let visible = $state(false);
|
||||||
|
|
@ -66,12 +67,12 @@
|
||||||
const h = includeTime ? selectedHour : 0;
|
const h = includeTime ? selectedHour : 0;
|
||||||
const m = includeTime ? selectedMinute : 0;
|
const m = includeTime ? selectedMinute : 0;
|
||||||
const iso = new Date(viewYear, viewMonth, selectedDay, h, m).toISOString();
|
const iso = new Date(viewYear, viewMonth, selectedDay, h, m).toISOString();
|
||||||
onchange(iso);
|
onchange(iso, includeTime);
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
onchange(null);
|
onchange(null, false);
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
let title = $state("");
|
let title = $state("");
|
||||||
let description = $state("");
|
let description = $state("");
|
||||||
let dueDate = $state<string | null>(null);
|
let dueDate = $state<string | null>(null);
|
||||||
|
let dueDateHasTime = $state(false);
|
||||||
let inputEl = $state<HTMLInputElement | null>(null);
|
let inputEl = $state<HTMLInputElement | null>(null);
|
||||||
let showDatePicker = $state(false);
|
let showDatePicker = $state(false);
|
||||||
|
|
||||||
|
|
@ -17,11 +18,12 @@
|
||||||
if (!title.trim()) return;
|
if (!title.trim()) return;
|
||||||
const created = await app.createTask(title.trim(), description.trim() || undefined);
|
const created = await app.createTask(title.trim(), description.trim() || undefined);
|
||||||
if (dueDate && created) {
|
if (dueDate && created) {
|
||||||
await app.updateTask({ ...created, due_date: dueDate, updated_at: new Date().toISOString() });
|
await app.updateTask({ ...created, due_date: dueDate, has_time: dueDateHasTime, updated_at: new Date().toISOString() });
|
||||||
}
|
}
|
||||||
title = "";
|
title = "";
|
||||||
description = "";
|
description = "";
|
||||||
dueDate = null;
|
dueDate = null;
|
||||||
|
dueDateHasTime = false;
|
||||||
newTaskState.open = false;
|
newTaskState.open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,11 +32,13 @@
|
||||||
title = "";
|
title = "";
|
||||||
description = "";
|
description = "";
|
||||||
dueDate = null;
|
dueDate = null;
|
||||||
|
dueDateHasTime = false;
|
||||||
showDatePicker = false;
|
showDatePicker = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDateChange(iso: string | null) {
|
function handleDateChange(iso: string | null, hasTime: boolean = false) {
|
||||||
dueDate = iso;
|
dueDate = iso;
|
||||||
|
dueDateHasTime = hasTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDateChip(iso: string): string {
|
function formatDateChip(iso: string): string {
|
||||||
|
|
@ -43,8 +47,7 @@
|
||||||
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
const day = dayNames[d.getDay()];
|
const day = dayNames[d.getDay()];
|
||||||
const pad = (n: number) => String(n).padStart(2, "0");
|
const pad = (n: number) => String(n).padStart(2, "0");
|
||||||
const hasTime = d.getHours() !== 0 || d.getMinutes() !== 0;
|
const timePart = dueDateHasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
|
||||||
const timePart = hasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
|
|
||||||
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
|
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
|
||||||
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
|
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +140,7 @@
|
||||||
{#if showDatePicker}
|
{#if showDatePicker}
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
value={dueDate}
|
value={dueDate}
|
||||||
|
has_time={dueDateHasTime}
|
||||||
onchange={handleDateChange}
|
onchange={handleDateChange}
|
||||||
onclose={() => (showDatePicker = false)}
|
onclose={() => (showDatePicker = false)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@
|
||||||
debouncedSave({ description });
|
debouncedSave({ description });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDateChange(iso: string | null) {
|
function handleDateChange(iso: string | null, hasTime: boolean = false) {
|
||||||
app.updateTask({ ...task, due_date: iso, updated_at: new Date().toISOString() });
|
app.updateTask({ ...task, due_date: iso, has_time: hasTime, updated_at: new Date().toISOString() });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleToggle() {
|
async function handleToggle() {
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
const day = dayNames[d.getDay()];
|
const day = dayNames[d.getDay()];
|
||||||
const pad = (n: number) => String(n).padStart(2, "0");
|
const pad = (n: number) => String(n).padStart(2, "0");
|
||||||
const hasTime = d.getHours() !== 0 || d.getMinutes() !== 0;
|
const hasTime = task.has_time;
|
||||||
const timePart = hasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
|
const timePart = hasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
|
||||||
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
|
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
|
||||||
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
|
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
|
||||||
|
|
@ -223,6 +223,7 @@
|
||||||
{#if showDatePicker}
|
{#if showDatePicker}
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
value={task.due_date}
|
value={task.due_date}
|
||||||
|
has_time={task.has_time}
|
||||||
onchange={handleDateChange}
|
onchange={handleDateChange}
|
||||||
onclose={() => (showDatePicker = false)}
|
onclose={() => (showDatePicker = false)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export interface Task {
|
||||||
description: string;
|
description: string;
|
||||||
status: "backlog" | "completed";
|
status: "backlog" | "completed";
|
||||||
due_date: string | null;
|
due_date: string | null;
|
||||||
|
has_time: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
parent_id: string | null;
|
parent_id: string | null;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ pub struct Task {
|
||||||
pub status: TaskStatus,
|
pub status: TaskStatus,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub due_date: Option<DateTime<Utc>>,
|
pub due_date: Option<DateTime<Utc>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub has_time: bool,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -32,6 +34,7 @@ impl Task {
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
status: TaskStatus::Backlog,
|
status: TaskStatus::Backlog,
|
||||||
due_date: None,
|
due_date: None,
|
||||||
|
has_time: false,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ pub struct TaskFrontmatter {
|
||||||
pub status: TaskStatus,
|
pub status: TaskStatus,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub due: Option<DateTime<Utc>>,
|
pub due: Option<DateTime<Utc>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub has_time: bool,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub updated: DateTime<Utc>,
|
pub updated: DateTime<Utc>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -68,6 +70,7 @@ impl From<&Task> for TaskFrontmatter {
|
||||||
id: task.id,
|
id: task.id,
|
||||||
status: task.status,
|
status: task.status,
|
||||||
due: task.due_date,
|
due: task.due_date,
|
||||||
|
has_time: task.has_time,
|
||||||
created: task.created_at,
|
created: task.created_at,
|
||||||
updated: task.updated_at,
|
updated: task.updated_at,
|
||||||
parent: task.parent_id,
|
parent: task.parent_id,
|
||||||
|
|
@ -256,6 +259,7 @@ impl Storage for FileSystemStorage {
|
||||||
description,
|
description,
|
||||||
status: frontmatter.status,
|
status: frontmatter.status,
|
||||||
due_date: frontmatter.due,
|
due_date: frontmatter.due,
|
||||||
|
has_time: frontmatter.has_time,
|
||||||
created_at: frontmatter.created,
|
created_at: frontmatter.created,
|
||||||
updated_at: frontmatter.updated,
|
updated_at: frontmatter.updated,
|
||||||
parent_id: frontmatter.parent,
|
parent_id: frontmatter.parent,
|
||||||
|
|
@ -344,6 +348,7 @@ impl Storage for FileSystemStorage {
|
||||||
description,
|
description,
|
||||||
status: frontmatter.status,
|
status: frontmatter.status,
|
||||||
due_date: frontmatter.due,
|
due_date: frontmatter.due,
|
||||||
|
has_time: frontmatter.has_time,
|
||||||
created_at: frontmatter.created,
|
created_at: frontmatter.created,
|
||||||
updated_at: frontmatter.updated,
|
updated_at: frontmatter.updated,
|
||||||
parent_id: frontmatter.parent,
|
parent_id: frontmatter.parent,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue