Skip to content

Commit 664f66c

Browse files
committed
add recurring event handling
1 parent bb7b3ad commit 664f66c

7 files changed

Lines changed: 303 additions & 51 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ dist-ssr
2323
*.njsproj
2424
*.sln
2525
*.sw?
26+
27+
# Files
28+
HIFI-Glass.png

src/components/AddEntryDialog.tsx

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function AddEntryDialog({ onAddEntry, buttonClassName }: AddEntryDialogPr
3737
const [time, setTime] = useState("");
3838
const [provider, setProvider] = useState("");
3939
const [recurring, setRecurring] = useState(false);
40+
const [recurrence, setRecurrence] = useState<TimelineEntryData['recurring']>('daily');
4041

4142
const handleSubmit = (e: React.FormEvent) => {
4243
e.preventDefault();
@@ -58,7 +59,7 @@ export function AddEntryDialog({ onAddEntry, buttonClassName }: AddEntryDialogPr
5859
status: "upcoming",
5960
provider: provider || undefined,
6061
date: date,
61-
recurring: recurring,
62+
recurring: recurring ? recurrence : undefined,
6263
});
6364

6465
// Reset form
@@ -68,6 +69,7 @@ export function AddEntryDialog({ onAddEntry, buttonClassName }: AddEntryDialogPr
6869
setTime("");
6970
setProvider("");
7071
setRecurring(false);
72+
setRecurrence('daily');
7173
setOpen(false);
7274
};
7375

@@ -158,18 +160,28 @@ export function AddEntryDialog({ onAddEntry, buttonClassName }: AddEntryDialogPr
158160
/>
159161
</div>
160162

161-
<div className="flex items-center space-x-2">
162-
<Checkbox
163-
id="recurring"
164-
checked={recurring}
165-
onCheckedChange={(checked) => setRecurring(checked as boolean)}
166-
/>
167-
<Label
168-
htmlFor="recurring"
169-
className="text-sm font-normal cursor-pointer"
170-
>
171-
This is a recurring event
172-
</Label>
163+
<div className="flex items-center gap-4">
164+
<div className="flex items-center space-x-2">
165+
<Checkbox
166+
id="recurring"
167+
checked={recurring}
168+
onCheckedChange={(checked) => setRecurring(checked as boolean)}
169+
/>
170+
<Label htmlFor="recurring" className="text-sm font-normal cursor-pointer">
171+
Recurring Event
172+
</Label>
173+
</div>
174+
{recurring && (
175+
<Select value={recurrence} onValueChange={(value) => setRecurrence(value as TimelineEntryData['recurring'])}>
176+
<SelectTrigger id="recurrence" className="h-9">
177+
<SelectValue />
178+
</SelectTrigger>
179+
<SelectContent>
180+
<SelectItem value="daily">Daily</SelectItem>
181+
<SelectItem value="weekly">Weekly</SelectItem>
182+
</SelectContent>
183+
</Select>
184+
)}
173185
</div>
174186

175187
<div className="flex justify-end gap-2">

src/components/ChatbotWidget.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,7 @@ export function ChatbotWidget() {
5454
<div className="fixed bottom-6 right-6 z-50">
5555
{isOpen && (
5656
<div className="mb-4 w-80 rounded-lg border bg-card shadow-lg">
57-
<div className="flex items-center justify-between rounded-t-lg border-b bg-slate-900 p-4">
58-
<h3 className="font-semibold text-white">AI Assistant</h3>
59-
<Button size="icon" variant="ghost" onClick={() => setIsOpen(false)} className="text-white">
60-
<X className="h-4 w-4" />
61-
</Button>
62-
</div>
57+
<h3 className="rounded-t-lg border-b bg-slate-900 p-3 font-semibold text-white">AI Assistant</h3>
6358

6459
<div className="h-96 space-y-4 overflow-y-auto p-4">
6560
{messages.map((m, i) => (
@@ -102,7 +97,7 @@ export function ChatbotWidget() {
10297
<Button
10398
onClick={() => setIsOpen(!isOpen)}
10499
size="icon"
105-
className={cn("h-14 w-14 rounded-full shadow-lg transition-all bg-slate-900 hover:bg-slate-800", isOpen && "rotate-0")}
100+
className="h-14 w-14 rounded-full bg-slate-900 shadow-lg transition-all hover:bg-slate-800"
106101
>
107102
{isOpen ? <X className="h-6 w-6" /> : <MessageCircle className="h-6 w-6" />}
108103
</Button>

src/components/EditEntryDialog.tsx

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { useState, useEffect } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogHeader,
8+
DialogTitle,
9+
DialogFooter,
10+
} from "@/components/ui/dialog";
11+
import { Input } from "@/components/ui/input";
12+
import { Label } from "@/components/ui/label";
13+
import { Textarea } from "@/components/ui/textarea";
14+
import { Checkbox } from "@/components/ui/checkbox";
15+
import {
16+
Select,
17+
SelectContent,
18+
SelectItem,
19+
SelectTrigger,
20+
SelectValue,
21+
} from "@/components/ui/select";
22+
import { TimelineEntryData } from "./TimelineEntry";
23+
import { format, parse } from "date-fns";
24+
25+
interface EditEntryDialogProps {
26+
entry: TimelineEntryData;
27+
isOpen: boolean;
28+
onClose: () => void;
29+
onUpdate: (entry: TimelineEntryData) => void;
30+
onRemove: (id: string) => void;
31+
}
32+
33+
export function EditEntryDialog({ entry, isOpen, onClose, onUpdate, onRemove }: EditEntryDialogProps) {
34+
const [type, setType] = useState<TimelineEntryData["type"]>(entry.type);
35+
const [title, setTitle] = useState(entry.title);
36+
const [description, setDescription] = useState(entry.description);
37+
const [date, setDate] = useState(entry.date);
38+
const [time, setTime] = useState(() => format(parse(entry.time, "hh:mm a", new Date()), "HH:mm"));
39+
const [provider, setProvider] = useState(entry.provider || "");
40+
const [recurring, setRecurring] = useState(!!entry.recurring);
41+
const [recurrence, setRecurrence] = useState<TimelineEntryData['recurring']>(entry.recurring || 'daily');
42+
43+
useEffect(() => {
44+
if (isOpen) {
45+
setType(entry.type);
46+
setTitle(entry.title);
47+
setDescription(entry.description);
48+
setDate(entry.date);
49+
setTime(format(parse(entry.time, "hh:mm a", new Date()), "HH:mm"));
50+
setProvider(entry.provider || "");
51+
setRecurring(!!entry.recurring);
52+
setRecurrence(entry.recurring || 'daily');
53+
}
54+
}, [entry, isOpen]);
55+
56+
const handleUpdate = (e: React.FormEvent) => {
57+
e.preventDefault();
58+
if (!title || !description || !date || !time) return;
59+
60+
const [hours, minutes] = time.split(":");
61+
const hoursNum = parseInt(hours, 10);
62+
const ampm = hoursNum >= 12 ? "PM" : "AM";
63+
const formattedHours = hoursNum % 12 || 12;
64+
const formattedTime = `${String(formattedHours).padStart(2, '0')}:${minutes} ${ampm}`;
65+
66+
onUpdate({
67+
...entry,
68+
type,
69+
title,
70+
description,
71+
time: formattedTime,
72+
provider: provider || undefined,
73+
date: date,
74+
recurring: recurring ? recurrence : undefined,
75+
});
76+
};
77+
78+
const handleDelete = () => {
79+
if (window.confirm("Are you sure you want to delete this entry?")) {
80+
onRemove(entry.id);
81+
onClose();
82+
}
83+
};
84+
85+
return (
86+
<Dialog open={isOpen} onOpenChange={onClose}>
87+
<DialogContent className="sm:max-w-[500px]">
88+
<DialogHeader>
89+
<DialogTitle>Edit Timeline Entry</DialogTitle>
90+
<DialogDescription>
91+
Update the details for this entry or delete it.
92+
</DialogDescription>
93+
</DialogHeader>
94+
<form onSubmit={handleUpdate} className="space-y-4">
95+
<div className="space-y-2">
96+
<Label htmlFor="type">Entry Type</Label>
97+
<Select value={type} onValueChange={(value) => setType(value as TimelineEntryData["type"])}>
98+
<SelectTrigger id="type"><SelectValue /></SelectTrigger>
99+
<SelectContent>
100+
<SelectItem value="medication">Medication</SelectItem>
101+
<SelectItem value="lab">Lab Result</SelectItem>
102+
<SelectItem value="appointment">Appointment</SelectItem>
103+
</SelectContent>
104+
</Select>
105+
</div>
106+
107+
<div className="space-y-2">
108+
<Label htmlFor="title">Title</Label>
109+
<Input id="title" value={title} onChange={(e) => setTitle(e.target.value)} required />
110+
</div>
111+
112+
<div className="space-y-2">
113+
<Label htmlFor="description">Description</Label>
114+
<Textarea id="description" value={description} onChange={(e) => setDescription(e.target.value)} required />
115+
</div>
116+
117+
<div className="grid grid-cols-2 gap-4">
118+
<div className="space-y-2">
119+
<Label htmlFor="date">Date</Label>
120+
<Input id="date" type="date" value={date} onChange={(e) => setDate(e.target.value)} required />
121+
</div>
122+
<div className="space-y-2">
123+
<Label htmlFor="time">Time</Label>
124+
<Input id="time" type="time" value={time} onChange={(e) => setTime(e.target.value)} required />
125+
</div>
126+
</div>
127+
128+
<div className="space-y-2">
129+
<Label htmlFor="provider">Provider (Optional)</Label>
130+
<Input id="provider" value={provider} onChange={(e) => setProvider(e.target.value)} />
131+
</div>
132+
133+
<div className="flex items-center gap-4">
134+
<div className="flex items-center space-x-2">
135+
<Checkbox id="recurring" checked={recurring} onCheckedChange={(checked) => setRecurring(checked as boolean)} />
136+
<Label htmlFor="recurring" className="text-sm font-normal cursor-pointer">Recurring Event</Label>
137+
</div>
138+
{recurring && (
139+
<Select value={recurrence} onValueChange={(value) => setRecurrence(value as TimelineEntryData['recurring'])}>
140+
<SelectTrigger id="recurrence" className="h-9"><SelectValue /></SelectTrigger>
141+
<SelectContent>
142+
<SelectItem value="daily">Daily</SelectItem>
143+
<SelectItem value="weekly">Weekly</SelectItem>
144+
</SelectContent>
145+
</Select>
146+
)}
147+
</div>
148+
149+
<DialogFooter className="gap-2 sm:justify-between">
150+
<Button type="button" variant="destructive" onClick={handleDelete}>Delete</Button>
151+
<div className="flex gap-2">
152+
<Button type="button" variant="outline" onClick={onClose}>Cancel</Button>
153+
<Button type="submit">Save Changes</Button>
154+
</div>
155+
</DialogFooter>
156+
</form>
157+
</DialogContent>
158+
</Dialog>
159+
);
160+
}

src/components/TimelineEntry.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
Pill, Calendar, FlaskConical, CheckCircle2, XCircle, Clock, LucideIcon, Trash2,
2+
Pill, Calendar, FlaskConical, CheckCircle2, XCircle, Clock, LucideIcon, Trash2, Edit,
33
} from "lucide-react";
44
import { cn } from "@/lib/utils";
55

@@ -12,13 +12,14 @@ export interface TimelineEntryData {
1212
time: string;
1313
status: EntryStatus;
1414
provider?: string;
15-
date?: string;
16-
recurring?: boolean;
15+
date: string;
16+
recurring?: 'daily' | 'weekly';
1717
}
1818

1919
interface TimelineEntryProps {
2020
entry: TimelineEntryData;
2121
onStatusChange: (id: string, newStatus: EntryStatus) => void;
22+
onEdit: (id: string) => void;
2223
onRemove: (id: string) => void;
2324
isLast?: boolean;
2425
}
@@ -53,7 +54,7 @@ const getStatusToggle = (type: TimelineEntryData["type"], status: EntryStatus):
5354
return { label: "", next: status };
5455
};
5556

56-
export const TimelineEntry = ({ entry, onStatusChange, onRemove, isLast }: TimelineEntryProps) => {
57+
export const TimelineEntry = ({ entry, onStatusChange, onEdit, onRemove, isLast }: TimelineEntryProps) => {
5758
const isToggleable = entry.type === "medication" || entry.type === "appointment";
5859
const config = typeConfig[entry.type];
5960
const StatusIcon = statusIcons[entry.status];
@@ -66,7 +67,7 @@ export const TimelineEntry = ({ entry, onStatusChange, onRemove, isLast }: Timel
6667
{!isLast && (
6768
<span
6869
aria-hidden="true"
69-
className="pointer-events-none absolute left-[36px] top-[72px] h-[calc(100%-36px)] w-px bg-white/55"
70+
className="pointer-events-none absolute left-[28px] top-[70px] h-[calc(100%-40px)] w-px bg-slate-300/70"
7071
/>
7172
)}
7273

@@ -99,6 +100,9 @@ export const TimelineEntry = ({ entry, onStatusChange, onRemove, isLast }: Timel
99100
{statusToggle.label}
100101
</button>
101102
)}
103+
{onEdit && (
104+
<button type="button" onClick={() => onEdit(entry.id)} className="text-slate-400 hover:text-slate-700"><Edit className="h-4 w-4" /></button>
105+
)}
102106
<button type="button" onClick={() => onRemove(entry.id)} className="text-slate-400 hover:text-red-500">
103107
<Trash2 className="h-4 w-4" />
104108
<span className="sr-only">Remove entry</span>

0 commit comments

Comments
 (0)