feat: browse archived habit history and restore habits
This commit is contained in:
53
src/components/ArchivedHabits.tsx
Normal file
53
src/components/ArchivedHabits.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { habitRequest, type TodayHabit } from "../lib/dashboard";
|
||||
import { Button, SectionHeading } from "./design-system/primitives";
|
||||
import { Card } from "./design-system/Card";
|
||||
import { HabitHistory } from "./HabitHistory";
|
||||
|
||||
export function ArchivedHabits({ date, revision, onChanged, onExpired }: {
|
||||
date: string; revision: number; onChanged: () => void; onExpired: () => void;
|
||||
}) {
|
||||
const [habits, setHabits] = useState<TodayHabit[]>([]);
|
||||
const [selected, setSelected] = useState<string>();
|
||||
const [error, setError] = useState("");
|
||||
const [notice, setNotice] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [attempt, setAttempt] = useState(0);
|
||||
const saving = useRef(false);
|
||||
const expired = useRef(onExpired); expired.current = onExpired;
|
||||
useEffect(() => {
|
||||
const controller = new AbortController(); setLoading(true); setError("");
|
||||
habitRequest<{ habits: TodayHabit[] }>(`/days/${date}`, { signal: controller.signal })
|
||||
.then(data => { if (!controller.signal.aborted) setHabits(data.habits.filter(habit => habit.requirements?.archived)); })
|
||||
.catch(error => { if (!controller.signal.aborted) { setError(error.message); if (error.message.includes("session has expired")) expired.current(); } })
|
||||
.finally(() => { if (!controller.signal.aborted) setLoading(false); });
|
||||
return () => controller.abort();
|
||||
}, [date, revision, attempt]);
|
||||
async function restore(habit: TodayHabit) {
|
||||
if (saving.current) return;
|
||||
saving.current = true; setBusy(true); setError(""); setNotice("");
|
||||
try {
|
||||
await habitRequest(`/habits/${habit.habitId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ archived: false }) });
|
||||
setHabits(current => current.filter(item => item.habitId !== habit.habitId));
|
||||
setNotice(`${habit.name} restored. Check-ins resume today.`); onChanged();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Could not restore. Try again.";
|
||||
setError(message); if (message.includes("session has expired")) expired.current();
|
||||
} finally { saving.current = false; setBusy(false); }
|
||||
}
|
||||
return <section className="ds-section" id="archived-habits" aria-label="Archived habits">
|
||||
<SectionHeading number="YOUR ARCHIVE" title="Archived habits" />
|
||||
<p>Restore a habit to resume its schedule today. Earlier history stays available.</p>
|
||||
{error && <p role="alert">{error} <Button onClick={() => setAttempt(value => value + 1)}>Retry archive</Button></p>}
|
||||
{loading ? <p role="status">Loading archive…</p> : !habits.length && <p>No archived habits.</p>}
|
||||
<p role="status">{notice}</p>
|
||||
{habits.map(habit => <Card key={habit.habitId} heading={habit.name ?? "Habit"}>
|
||||
<div className="ds-actions">
|
||||
<Button variant="secondary" disabled={busy || loading} onClick={() => void restore(habit)}>Restore {habit.name}</Button>
|
||||
<Button variant="text" onClick={() => setSelected(selected === habit.habitId ? undefined : habit.habitId)} aria-expanded={selected === habit.habitId}>{selected === habit.habitId ? "Hide history" : "View history"}</Button>
|
||||
</div>
|
||||
{selected === habit.habitId && <HabitHistory habit={habit} date={date} revision={revision + attempt} disabled={busy || loading} onExpired={onExpired} onHistorySaved={() => setAttempt(value => value + 1)} />}
|
||||
</Card>)}
|
||||
</section>;
|
||||
}
|
||||
Reference in New Issue
Block a user