Skip to content

Commit 775ea9c

Browse files
committed
feat: integrate diary entries into MyDay calendar and refactor medication management in patient profile
1 parent 3f5f4c2 commit 775ea9c

5 files changed

Lines changed: 279 additions & 90 deletions

File tree

src/components/dashboard/DayTimeline.jsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,17 @@ function MedicationBlock({ med, selectedDate, onToggle, onEdit }) {
232232
className={`mb-1 rounded-lg px-3 py-2 border flex items-center gap-3 text-sm transition-colors ${statusStyles}`}
233233
>
234234
<div className="flex items-center gap-3 flex-1 min-w-0">
235-
<Checkbox
236-
checked={isTaken}
237-
onCheckedChange={() => onToggle?.(med.data, format(selectedDate, 'yyyy-MM-dd'))}
238-
className={`h-5 w-5 rounded-md shrink-0 ${isTaken ? 'border-green-500' : isMissed ? 'border-red-400' : 'border-yellow-500'}`}
239-
/>
235+
{onToggle ? (
236+
<Checkbox
237+
checked={isTaken}
238+
onCheckedChange={() => onToggle?.(med.data, format(selectedDate, 'yyyy-MM-dd'))}
239+
className={`h-5 w-5 rounded-md shrink-0 ${isTaken ? 'border-green-500' : isMissed ? 'border-red-400' : 'border-yellow-500'}`}
240+
/>
241+
) : (
242+
<div className={`h-5 w-5 rounded-md border shrink-0 flex items-center justify-center ${isTaken ? 'bg-green-500 border-green-500' : isMissed ? 'border-red-400' : 'border-yellow-500'}`}>
243+
{isTaken && <CheckCircle2 className="w-3 h-3 text-white" />}
244+
</div>
245+
)}
240246
<div className="shrink-0">
241247
<span className="text-xs font-bold opacity-80">{med.time}</span>
242248
</div>

src/pages/Diary.jsx

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from 'react';
22
import { apiClient } from '@/api/client';
33
import { useAuth } from '@/lib/AuthContext';
44
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
5-
import { format, parseISO, subDays, isToday } from 'date-fns';
5+
import { format, parseISO, subDays, isToday, eachDayOfInterval, startOfDay } from 'date-fns';
66
import { motion, AnimatePresence } from 'framer-motion';
77
import { toast } from 'sonner';
88
import { Button } from '@/components/ui/button';
@@ -24,21 +24,24 @@ const MOODS = [
2424
const getMood = (value) => MOODS.find(m => m.value === value) || MOODS[2];
2525

2626
// ─── Mini Mood Bar Chart ─────────────────────────────────────────────────────
27-
function MoodChart({ entries }) {
27+
function MoodChart({ entries, onDateSelect }) {
2828
const last7 = Array.from({ length: 7 }, (_, i) => {
2929
const date = subDays(new Date(), 6 - i);
3030
const dateStr = format(date, 'yyyy-MM-dd');
3131
const entry = entries.find(e => e.date === dateStr);
32-
return { date, mood: entry?.mood_score ?? null };
32+
return { date, dateStr, mood: entry?.mood_score ?? null };
3333
});
3434

3535
return (
3636
<div className="flex items-end gap-1.5 h-12">
37-
{last7.map(({ date, mood }, i) => {
37+
{last7.map(({ date, dateStr, mood }, i) => {
3838
const m = mood ? getMood(mood) : null;
3939
return (
4040
<div key={i} className="flex flex-col items-center gap-1 flex-1">
41-
<motion.div
41+
<motion.button
42+
whileHover={{ scale: 1.1, y: -2 }}
43+
whileTap={{ scale: 0.9 }}
44+
onClick={() => onDateSelect?.(dateStr)}
4245
initial={{ scaleY: 0 }}
4346
animate={{ scaleY: 1 }}
4447
transition={{ delay: i * 0.05, duration: 0.4, ease: 'easeOut' }}
@@ -47,7 +50,8 @@ function MoodChart({ entries }) {
4750
backgroundColor: m ? m.color : '#e2e8f0',
4851
originY: 1,
4952
}}
50-
className="w-full rounded-t-sm"
53+
className="w-full rounded-t-sm cursor-pointer"
54+
title={mood ? `Mood: ${m.label} on ${format(date, 'MMM d')}` : `No entry for ${format(date, 'MMM d')}`}
5155
/>
5256
<span className="text-[9px] text-muted-foreground font-medium">
5357
{format(date, 'EEE')[0]}
@@ -165,10 +169,10 @@ function EntryCard({ entry, onEdit }) {
165169
<Button
166170
variant="ghost"
167171
size="icon"
168-
className="h-7 w-7 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
172+
className="h-8 w-8 transition-all shrink-0 hover:bg-black/5 rounded-full"
169173
onClick={() => onEdit(entry)}
170174
>
171-
<Pencil className="w-3.5 h-3.5" />
175+
<Pencil className="w-4 h-4 text-muted-foreground" />
172176
</Button>
173177
)}
174178
</div>
@@ -277,20 +281,7 @@ export default function Diary() {
277281
}
278282
}, [isDoctor, todayEntry, mode]);
279283

280-
if (isDoctor) {
281-
return (
282-
<div className="max-w-xl mx-auto py-16 px-4 text-center space-y-4 text-muted-foreground">
283-
<BookOpen className="w-10 h-10 mx-auto opacity-30" />
284-
<p className="text-sm">Diary entries are available in each patient's profile.</p>
285-
<Button asChild variant="outline" className="rounded-full gap-2">
286-
<Link to="/patient-logs">
287-
<Users className="w-4 h-4" />
288-
Go to Patient Logs
289-
</Link>
290-
</Button>
291-
</div>
292-
);
293-
}
284+
294285

295286
// ── Mutation ─────────────────────────────────────────────────────────────
296287
const saveMutation = useMutation({
@@ -354,10 +345,37 @@ export default function Diary() {
354345
saveMutation.mutate({ entryId: editingEntry.id, mood: selectedMood, text: notes, date: editingEntry.date });
355346
};
356347

357-
const avgMood = entries.length > 0
358-
? (entries.slice(0, 7).reduce((s, e) => s + e.mood_score, 0) / Math.min(entries.length, 7)).toFixed(1)
348+
const avgMood = entries.length > 0
349+
? (entries.reduce((s, e) => s + e.mood_score, 0) / entries.length).toFixed(1)
359350
: null;
360351

352+
if (isDoctor) {
353+
return (
354+
<motion.div
355+
initial={{ opacity: 0, y: 15 }}
356+
animate={{ opacity: 1, y: 0 }}
357+
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
358+
className="max-w-xl mx-auto py-16 px-4 text-center space-y-6"
359+
>
360+
<div className="w-20 h-20 bg-violet-50 rounded-3xl flex items-center justify-center mx-auto mb-6">
361+
<BookOpen className="w-10 h-10 text-violet-300" />
362+
</div>
363+
<div className="space-y-2">
364+
<h2 className="text-xl font-bold font-heading">Patient Diary View</h2>
365+
<p className="text-sm text-muted-foreground max-w-xs mx-auto">
366+
Diary entries reach you directly via each patient's profile clinical dashboard.
367+
</p>
368+
</div>
369+
<Button asChild variant="outline" className="rounded-full gap-2 h-11 px-6 font-bold shadow-sm hover:shadow-md transition-all">
370+
<Link to="/patient-logs">
371+
<Users className="w-4 h-4" />
372+
View Patient Logs
373+
</Link>
374+
</Button>
375+
</motion.div>
376+
);
377+
}
378+
361379
const showSuccessCard = mode === 'view' && todayEntry;
362380
const showInlineForm = mode === 'editing';
363381

@@ -374,7 +392,16 @@ export default function Diary() {
374392
<BookOpen className="w-6 h-6 text-violet-500" />
375393
My Diary
376394
</h1>
377-
<p className="text-sm text-muted-foreground mt-0.5">How are you feeling today?</p>
395+
<div className="flex items-center justify-between mt-0.5">
396+
<p className="text-sm text-muted-foreground">How are you feeling today?</p>
397+
<Button
398+
variant="link"
399+
className="h-auto p-0 text-violet-600 text-xs font-bold"
400+
onClick={() => document.getElementById('previous-entries')?.scrollIntoView({ behavior: 'smooth' })}
401+
>
402+
View History ↓
403+
</Button>
404+
</div>
378405
</div>
379406

380407
{/* 7-day Mood Trend */}
@@ -391,12 +418,27 @@ export default function Diary() {
391418
</Badge>
392419
)}
393420
</div>
394-
<MoodChart entries={entries} />
421+
<MoodChart
422+
entries={entries}
423+
onDateSelect={(dateStr) => {
424+
const el = document.getElementById(`entry-${dateStr}`);
425+
if (el) {
426+
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
427+
el.classList.add('ring-2', 'ring-violet-400', 'ring-offset-2');
428+
setTimeout(() => el.classList.remove('ring-2', 'ring-violet-400', 'ring-offset-2'), 2000);
429+
} else if (dateStr === todayStr) {
430+
document.getElementById('today-section')?.scrollIntoView({ behavior: 'smooth' });
431+
} else {
432+
toast.info(`No entry found for ${format(parseISO(dateStr), 'MMM d')}`);
433+
}
434+
}}
435+
/>
395436
</div>
396437
)}
397438

398439
{/* Today: success card OR inline form */}
399-
<AnimatePresence mode="wait">
440+
<div id="today-section">
441+
<AnimatePresence mode="wait">
400442
{showSuccessCard ? (
401443
<motion.div
402444
key="success"
@@ -538,19 +580,22 @@ export default function Diary() {
538580
</motion.div>
539581
) : null}
540582
</AnimatePresence>
583+
</div>
541584

542585
{/* Past Entries */}
543586
{entries.filter(e => e.date !== todayStr).length > 0 && (
544587
<div className="space-y-3">
545-
<p className="text-xs font-bold text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
588+
<p id="previous-entries" className="text-xs font-bold text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
546589
<Calendar className="w-3.5 h-3.5" />
547590
Previous entries
548591
</p>
549592
<div className="space-y-2">
550593
{entries
551594
.filter(e => e.date !== todayStr)
552595
.map(entry => (
553-
<EntryCard key={entry.id} entry={entry} onEdit={openPastEdit} />
596+
<div key={entry.id} id={`entry-${entry.date}`}>
597+
<EntryCard entry={entry} onEdit={openPastEdit} />
598+
</div>
554599
))}
555600
</div>
556601
</div>

src/pages/Landing.jsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ export default function Landing() {
178178
>
179179
See how it works
180180
</motion.button>
181+
<motion.button
182+
whileHover={{ scale: 1.05, shadow: "0px 10px 30px rgba(30, 45, 78, 0.2)" }}
183+
whileTap={{ scale: 0.95 }}
184+
onClick={() => scrollTo('reviews-section')}
185+
style={{ backgroundColor: '#1E2D4E' }}
186+
className="text-white px-8 py-4 rounded-xl font-medium text-lg hover:opacity-90 transition-all shadow-lg shadow-slate-900/10"
187+
>
188+
Leave a review
189+
</motion.button>
181190
</motion.div>
182191
</section>
183192
</div>{/* end hero gradient wrapper */}
@@ -320,7 +329,7 @@ export default function Landing() {
320329
</section>
321330

322331
{/* Reviews Section */}
323-
<section className="px-8 py-32 bg-primary/5">
332+
<section id="reviews-section" className="px-8 py-32 bg-primary/5">
324333
<div className="max-w-3xl mx-auto">
325334
<motion.div
326335
initial={{ y: 30, opacity: 0 }}

src/pages/Myday.jsx

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22
// This is the daily schedule/timeline view (medications, appointments).
33
// The "Diary" name now belongs to the mood & journal feature.
44

5-
import { useState, useMemo, useEffect } from 'react';
5+
import { useState, useMemo } from 'react';
66
import { apiClient } from '@/api/client';
77
import { useAuth } from '@/lib/AuthContext';
88
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
9-
import { Card, CardContent } from '@/components/ui/card';
109
import { Button } from '@/components/ui/button';
1110
import { Badge } from '@/components/ui/badge';
1211
import { Checkbox } from '@/components/ui/checkbox';
13-
import { ChevronLeft, ChevronRight, Pill, Calendar as CalIcon, Clock, FileText, CheckCircle2, Pencil } from 'lucide-react';
12+
import { ChevronLeft, ChevronRight, Pill, Clock, FileText, Pencil } from 'lucide-react';
1413
import {
1514
format, isSameDay, isWithinInterval, parseISO, addMonths, subMonths,
16-
startOfMonth, endOfMonth, eachDayOfInterval, getDay, startOfDay, addDays,
17-
subDays, isToday, startOfWeek, endOfWeek
15+
startOfMonth, endOfMonth, eachDayOfInterval, getDay, addDays,
16+
subDays, isToday
1817
} from 'date-fns';
1918
import { motion, AnimatePresence } from 'framer-motion';
2019
import { toast } from 'sonner';
@@ -29,8 +28,18 @@ const TYPE_COLORS = {
2928
medicationDone: 'bg-green-100 border-green-300 text-green-800',
3029
appointment: 'bg-emerald-100 border-emerald-200 text-emerald-700',
3130
form: 'bg-amber-100 border-amber-200 text-amber-700',
31+
diary: 'bg-violet-100 border-violet-200 text-violet-700',
3232
};
3333

34+
const MOODS = [
35+
{ value: 1, emoji: '😞', label: 'Rough', color: '#ef4444' },
36+
{ value: 2, emoji: '😕', label: 'Low', color: '#f97316' },
37+
{ value: 3, emoji: '😐', label: 'Okay', color: '#eab308' },
38+
{ value: 4, emoji: '🙂', label: 'Good', color: '#84cc16' },
39+
{ value: 5, emoji: '😄', label: 'Great', color: '#22c55e' },
40+
];
41+
const getMood = (v) => MOODS.find(m => m.value === v) || MOODS[2];
42+
3443
const getEventColor = (event) => {
3544
if (event.type === 'medication') {
3645
return event.completed ? TYPE_COLORS.medicationDone : TYPE_COLORS.medication;
@@ -75,6 +84,16 @@ export default function MyDay() {
7584
initialData: [],
7685
});
7786

87+
const { data: diaryEntries } = useQuery({
88+
queryKey: ['my-diary', user?.email],
89+
queryFn: async () => {
90+
const all = await apiClient.entities.DiaryEntry.filter();
91+
return all.filter(e => e.patient_email === user?.email);
92+
},
93+
enabled: !!user?.email,
94+
initialData: [],
95+
});
96+
7897
const toggleMedication = useMutation({
7998
mutationFn: async ({ task, dateStr }) => {
8099
const completed = task.completed_dates || [];
@@ -129,6 +148,19 @@ export default function MyDay() {
129148
}
130149
});
131150

151+
diaryEntries.forEach(e => {
152+
if (e.date === dateStr) {
153+
events.push({
154+
id: `diary-${e.id}`,
155+
type: 'diary',
156+
title: `Diary: ${getMood(e.mood_score).label}`,
157+
time: '08:00',
158+
duration: 45,
159+
data: e,
160+
});
161+
}
162+
});
163+
132164
return events.sort((a, b) => a.time.localeCompare(b.time));
133165
}, [medications, appointments, selectedDate]);
134166

@@ -226,22 +258,34 @@ export default function MyDay() {
226258
</div>
227259
</div>
228260
<div className="grid grid-cols-7 gap-y-1 text-center">
229-
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(d => (
230-
<div key={d} className="text-[10px] font-bold text-muted-foreground/60 uppercase">{d}</div>
261+
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((d, i) => (
262+
<div key={`${d}-${i}`} className="text-[10px] font-bold text-muted-foreground/60 uppercase">{d}</div>
231263
))}
232264
{Array.from({ length: startPadding }).map((_, i) => <div key={`pad-${i}`} />)}
233-
{calendarDays.map(day => (
234-
<button
235-
key={day.toISOString()}
236-
onClick={() => setSelectedDate(day)}
237-
className={`text-xs h-8 w-8 rounded-full flex items-center justify-center transition-all ${
238-
isSameDay(day, selectedDate) ? 'bg-primary text-primary-foreground font-bold' :
239-
isToday(day) ? 'text-primary font-bold' : 'hover:bg-muted text-foreground/80'
240-
}`}
241-
>
242-
{format(day, 'd')}
243-
</button>
244-
))}
265+
{calendarDays.map(day => {
266+
const dayStr = format(day, 'yyyy-MM-dd');
267+
const hasDiary = diaryEntries.some(e => e.date === dayStr);
268+
const mood = hasDiary ? getMood(diaryEntries.find(e => e.date === dayStr).mood_score) : null;
269+
270+
return (
271+
<button
272+
key={day.toISOString()}
273+
onClick={() => setSelectedDate(day)}
274+
className={`text-xs h-8 w-8 rounded-full flex flex-col items-center justify-center transition-all relative ${
275+
isSameDay(day, selectedDate) ? 'bg-primary text-primary-foreground font-bold' :
276+
isToday(day) ? 'text-primary font-bold' : 'hover:bg-muted text-foreground/80'
277+
}`}
278+
>
279+
{format(day, 'd')}
280+
{hasDiary && (
281+
<div
282+
className="absolute bottom-1 w-1 h-1 rounded-full"
283+
style={{ backgroundColor: mood.color }}
284+
/>
285+
)}
286+
</button>
287+
);
288+
})}
245289
</div>
246290
</motion.div>
247291

0 commit comments

Comments
 (0)