feat: implement archiving functionality for counters and update related logic
This commit is contained in:
+89
-5
@@ -34,6 +34,8 @@ export default function Home() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [historyCounterId, setHistoryCounterId] = useState<number | null>(null);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [archivedCounters, setArchivedCounters] = useState<Counter[]>([]);
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
|
||||
// Always-current ref so drag handlers never read stale closure state
|
||||
const countersRef = useRef(counters);
|
||||
@@ -65,6 +67,14 @@ export default function Home() {
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
useEffect(() => { router.prefetch('/stats'); }, [router]);
|
||||
useEffect(() => {
|
||||
if (showArchived) {
|
||||
fetch('/api/counters?archived=true')
|
||||
.then(r => r.json())
|
||||
.then(setArchivedCounters)
|
||||
.catch(() => {});
|
||||
}
|
||||
}, [showArchived]);
|
||||
|
||||
// ── Optimistic increment/decrement ─────────────────────────────────────────
|
||||
const handleIncrement = useCallback(async (id: number) => {
|
||||
@@ -86,7 +96,9 @@ export default function Home() {
|
||||
if (editingCounter) {
|
||||
// Optimistic update — close immediately, patch in background
|
||||
const optimistic = { ...editingCounter, ...data };
|
||||
setCounters(prev => prev.map(c => c.id === editingCounter.id ? optimistic : c));
|
||||
const isArchived = !!editingCounter.archived_at;
|
||||
const setTarget = isArchived ? setArchivedCounters : setCounters;
|
||||
setTarget(prev => prev.map(c => c.id === editingCounter.id ? optimistic : c));
|
||||
setModalOpen(false);
|
||||
setEditingCounter(null);
|
||||
const res = await fetch(`/api/counters/${editingCounter.id}`, {
|
||||
@@ -96,10 +108,9 @@ export default function Home() {
|
||||
});
|
||||
if (res.ok) {
|
||||
const updated = await res.json();
|
||||
setCounters(prev => prev.map(c => c.id === updated.id ? updated : c));
|
||||
setTarget(prev => prev.map(c => c.id === updated.id ? updated : c));
|
||||
} else {
|
||||
// Roll back
|
||||
setCounters(prev => prev.map(c => c.id === editingCounter.id ? editingCounter : c));
|
||||
setTarget(prev => prev.map(c => c.id === editingCounter.id ? editingCounter : c));
|
||||
}
|
||||
} else {
|
||||
// Optimistic create with a temporary id
|
||||
@@ -126,9 +137,41 @@ export default function Home() {
|
||||
// ── Delete counter ─────────────────────────────────────────────────────────
|
||||
async function handleDeleteCounter(id: number) {
|
||||
const prev = counters.find(c => c.id === id);
|
||||
const prevArchived = archivedCounters.find(c => c.id === id);
|
||||
setCounters(cs => cs.filter(c => c.id !== id));
|
||||
setArchivedCounters(cs => cs.filter(c => c.id !== id));
|
||||
const res = await fetch(`/api/counters/${id}`, { method: 'DELETE' });
|
||||
if (!res.ok && prev) setCounters(cs => [...cs, prev].sort((a, b) => a.order_index - b.order_index));
|
||||
if (!res.ok) {
|
||||
if (prev) setCounters(cs => [...cs, prev].sort((a, b) => a.order_index - b.order_index));
|
||||
if (prevArchived) setArchivedCounters(cs => [...cs, prevArchived]);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Archive / Unarchive counter ──────────────────────────────────────────────────────
|
||||
async function handleArchiveCounter(id: number) {
|
||||
const counter = counters.find(c => c.id === id);
|
||||
if (!counter) return;
|
||||
setCounters(cs => cs.filter(c => c.id !== id));
|
||||
const res = await fetch(`/api/counters/${id}/archive`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
const archived = await res.json();
|
||||
if (showArchived) setArchivedCounters(prev => [archived, ...prev]);
|
||||
} else {
|
||||
setCounters(cs => [...cs, counter].sort((a, b) => a.order_index - b.order_index));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUnarchiveCounter(id: number) {
|
||||
const counter = archivedCounters.find(c => c.id === id);
|
||||
if (!counter) return;
|
||||
setArchivedCounters(prev => prev.filter(c => c.id !== id));
|
||||
const res = await fetch(`/api/counters/${id}/unarchive`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
const unarchived = await res.json();
|
||||
setCounters(prev => [...prev, unarchived].sort((a, b) => a.order_index - b.order_index));
|
||||
} else {
|
||||
setArchivedCounters(prev => [...prev, counter]);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Groups ─────────────────────────────────────────────────────────────────
|
||||
@@ -275,6 +318,21 @@ export default function Home() {
|
||||
</svg>
|
||||
Stats
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setShowArchived(v => !v)}
|
||||
className={`flex items-center gap-1.5 px-4 py-2 text-sm rounded-lg border transition-colors ${
|
||||
showArchived
|
||||
? 'border-ctp-yellow/50 bg-ctp-yellow/10 text-ctp-yellow hover:bg-ctp-yellow/20'
|
||||
: 'border-ctp-surface1 text-ctp-subtext1 hover:bg-ctp-surface0'
|
||||
}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="21 8 21 21 3 21 3 8" />
|
||||
<rect x="1" y="3" width="22" height="5" />
|
||||
<line x1="10" y1="12" x2="14" y2="12" />
|
||||
</svg>
|
||||
Archived
|
||||
</button>
|
||||
{addingGroup ? (
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
@@ -396,6 +454,30 @@ export default function Home() {
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showArchived && (
|
||||
<div className="mt-10">
|
||||
<h2 className="text-lg font-bold text-ctp-overlay1 mb-3">Archived</h2>
|
||||
{archivedCounters.length === 0 ? (
|
||||
<p className="text-ctp-overlay0 text-sm">No archived counters.</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3 opacity-60">
|
||||
{archivedCounters.map(counter => (
|
||||
<CounterCard
|
||||
key={counter.id}
|
||||
counter={counter}
|
||||
onIncrement={() => {}}
|
||||
onDecrement={() => {}}
|
||||
onEdit={c => { setEditingCounter(c); setModalOpen(true); }}
|
||||
onHistory={setHistoryCounterId}
|
||||
onPrefetch={handlePrefetch}
|
||||
editMode={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<CounterModal
|
||||
@@ -405,6 +487,8 @@ export default function Home() {
|
||||
onClose={() => { setModalOpen(false); setEditingCounter(null); }}
|
||||
onSave={handleSaveCounter}
|
||||
onDelete={handleDeleteCounter}
|
||||
onArchive={handleArchiveCounter}
|
||||
onUnarchive={handleUnarchiveCounter}
|
||||
/>
|
||||
|
||||
<CounterDetailModal
|
||||
|
||||
Reference in New Issue
Block a user