diff --git a/apps/web/app/(dashboard)/daily-notes/page.tsx b/apps/web/app/(dashboard)/daily-notes/page.tsx new file mode 100644 index 0000000..b7e7b06 --- /dev/null +++ b/apps/web/app/(dashboard)/daily-notes/page.tsx @@ -0,0 +1,205 @@ +'use client'; + +import { useState, useEffect, useCallback, Suspense } from 'react'; +import { BookOpen, Plus, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; +import dynamic from 'next/dynamic'; +import { format, addDays, subDays } from 'date-fns'; +import { toast } from 'sonner'; +import { Button } from '@/components/ui/button'; +import { Card } from '@/components/ui/card'; + +const NoteEditor = dynamic( + () => import('@/components/notes/note-editor').then((m) => m.NoteEditor), + { + ssr: false, + loading: () => ( +
+
Loading editor...
+
+ ), + } +); + +interface Note { + id: string; + title: string; + content: string | null; + createdAt: string; + updatedAt: string; +} + +export default function DailyNotesPage() { + const [currentDate, setCurrentDate] = useState(new Date()); + const [note, setNote] = useState(null); + const [loading, setLoading] = useState(true); + const [creating, setCreating] = useState(false); + const [saveStatus, setSaveStatus] = useState<'Saved' | 'Saving' | 'Failed'>('Saved'); + + const dateStr = format(currentDate, 'yyyy-MM-dd'); + const displayDate = format(currentDate, 'EEEE, MMMM d, yyyy'); + + const fetchDailyNote = useCallback(async () => { + setLoading(true); + setNote(null); + try { + const res = await fetch(`/api/notes/daily?date=${dateStr}`); + if (!res.ok) throw new Error('Failed to fetch daily note'); + const data = await res.json(); + if (data.note) { + setNote(data.note); + } + } catch (err) { + console.error('Failed to fetch daily note:', err); + } finally { + setLoading(false); + } + }, [dateStr]); + + useEffect(() => { + fetchDailyNote(); + }, [fetchDailyNote]); + + async function handleCreateDailyNote() { + setCreating(true); + try { + const res = await fetch('/api/notes/daily', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ date: dateStr }), + }); + if (!res.ok) { + const text = await res.text(); + console.error('Failed to create daily note', text); + toast.error('Failed to create daily note'); + return; + } + const createdNote = await res.json(); + setNote(createdNote); + toast.success('Daily note created'); + } catch (err) { + console.error('Failed to create daily note:', err); + toast.error('Failed to create daily note'); + } finally { + setCreating(false); + } + } + + function navigate(delta: number) { + const next = delta > 0 ? addDays(currentDate, 1) : subDays(currentDate, 1); + setCurrentDate(next); + } + + function goToToday() { + setCurrentDate(new Date()); + } + + async function handleSave(content: string) { + if (!note) return; + setSaveStatus('Saving'); + try { + const res = await fetch(`/api/notes/${note.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content }), + }); + if (!res.ok) throw new Error('Failed to save'); + setSaveStatus('Saved'); + } catch (err) { + console.error('Failed to save note:', err); + setSaveStatus('Failed'); + toast.error('Failed to save note'); + } + } + + const isToday = dateStr === format(new Date(), 'yyyy-MM-dd'); + + return ( +
+
+
+

Daily Notes

+

+ {displayDate} +

+
+
+ + {!isToday && ( + + )} + +
+
+ + + {loading ? ( +
+ +
+ ) : note ? ( +
+
+
+

{note.title}

+ + {saveStatus} + +
+
+
+ +
+ Loading editor... +
+
+ } + > + { + setNote({ ...note, content }); + handleSave(content); + }} + /> + +
+
+ ) : ( +
+ +
+

No daily note yet

+

+ {isToday + ? 'Create your daily note to track what you accomplished today.' + : 'No daily note exists for this date.'} +

+
+ +
+ )} + + + ); +} diff --git a/apps/web/components/sidebar.tsx b/apps/web/components/sidebar.tsx index 85079ed..1c23967 100644 --- a/apps/web/components/sidebar.tsx +++ b/apps/web/components/sidebar.tsx @@ -10,6 +10,7 @@ import { Flame, FolderKanban, NotebookPen, + BookOpen, Share2, CalendarDays, Search, @@ -41,6 +42,7 @@ const navItems = [ { href: '/habits', label: 'Habits', icon: Flame }, { href: '/projects', label: 'Projects', icon: FolderKanban }, { href: '/notes', label: 'Notes', icon: NotebookPen }, + { href: '/daily-notes', label: 'Daily Note', icon: BookOpen }, { href: '/graph', label: 'Graph', icon: Share2 }, { href: '/calendar', label: 'Calendar', icon: CalendarDays }, { href: '/search', label: 'Search', icon: Search },