Skip to content

Commit e64f472

Browse files
Merge branch 'main' into export
2 parents 8b2e248 + acca002 commit e64f472

16 files changed

Lines changed: 721 additions & 328 deletions

.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

STYLEGUIDE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@
100100
- `TrendingUp` - Summary/analytics view
101101
- `Plus` - Add new entry action
102102
- `Download` - Export functionality
103-
- `Share2` - Share/send functionality
104103

105104
## Design Principles
106105

index.html

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
<!doctype html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8" />
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>Micro-Timeline - Unified Health Tracking</title>
7-
<meta name="description" content="Track medications, symptoms, lab results, and appointments across all your healthcare providers in one unified timeline." />
8-
<meta name="author" content="Micro-Timeline" />
93

10-
<meta property="og:title" content="Micro-Timeline - Unified Health Tracking" />
11-
<meta property="og:description" content="Track medications, symptoms, lab results, and appointments across all your healthcare providers in one unified timeline." />
12-
<meta property="og:type" content="website" />
13-
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Micro-Timeline - Unified Health Tracking</title>
8+
<meta name="description"
9+
content="Track medications, symptoms, lab results, and appointments across all your healthcare providers in one unified timeline." />
10+
<meta name="author" content="Micro-Timeline" />
1411

15-
<meta name="twitter:card" content="summary_large_image" />
16-
<meta name="twitter:site" content="@lovable_dev" />
17-
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
18-
</head>
12+
<meta property="og:title" content="Micro-Timeline - Unified Health Tracking" />
13+
<meta property="og:description"
14+
content="Track medications, symptoms, lab results, and appointments across all your healthcare providers in one unified timeline." />
15+
<meta property="og:type" content="website" />
16+
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
1917

20-
<body>
21-
<div id="root"></div>
22-
<script type="module" src="/src/main.tsx"></script>
23-
</body>
24-
</html>
18+
<meta name="twitter:card" content="summary_large_image" />
19+
<meta name="twitter:site" content="@lovable_dev" />
20+
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
21+
</head>
22+
23+
<body>
24+
<div id="root"></div>
25+
<script type="module" src="/src/main.tsx"></script>
26+
</body>
27+
28+
</html>

postcss.config.cjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
};

postcss.config.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

server/index.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ app.use(express.json());
99

1010
const GEMINI_KEY = process.env.GEMINI_API_KEY;
1111
if (!GEMINI_KEY) {
12-
console.warn("⚠️ Missing GEMINI_API_KEY in .env");
12+
console.warn("Missing GEMINI_API_KEY in .env");
1313
}
1414

1515
// Normalize an ISO datetime from separate date/time strings if needed
@@ -135,4 +135,4 @@ You are Lifeline's on-device health schedule assistant.
135135
});
136136

137137
const PORT = process.env.PORT || 3001;
138-
app.listen(PORT, () => console.log(`🔊 API on http://localhost:${PORT}`));
138+
app.listen(PORT, () => console.log(`API on http://localhost:${PORT}`));

src/components/AddEntryDialog.tsx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,46 @@ import {
2020
SelectValue,
2121
} from "@/components/ui/select";
2222
import { Plus } from "lucide-react";
23-
import { EntryType, TimelineEntryData } from "./TimelineEntry";
23+
import { TimelineEntryData } from "./TimelineEntry";
24+
import { cn } from "@/lib/utils";
2425

2526
interface AddEntryDialogProps {
2627
onAddEntry: (entry: Omit<TimelineEntryData, "id">) => void;
28+
buttonClassName?: string;
2729
}
2830

29-
export function AddEntryDialog({ onAddEntry }: AddEntryDialogProps) {
31+
export function AddEntryDialog({ onAddEntry, buttonClassName }: AddEntryDialogProps) {
3032
const [open, setOpen] = useState(false);
31-
const [type, setType] = useState<EntryType>("medication");
33+
const [type, setType] = useState<TimelineEntryData["type"]>("medication");
3234
const [title, setTitle] = useState("");
3335
const [description, setDescription] = useState("");
3436
const [date, setDate] = useState("");
3537
const [time, setTime] = useState("");
3638
const [provider, setProvider] = useState("");
3739
const [recurring, setRecurring] = useState(false);
40+
const [recurrence, setRecurrence] = useState<TimelineEntryData['recurring']>('daily');
3841

3942
const handleSubmit = (e: React.FormEvent) => {
4043
e.preventDefault();
41-
44+
4245
if (!title || !description || !date || !time) return;
4346

47+
// Format time to AM/PM
48+
const [hours, minutes] = time.split(":");
49+
const hoursNum = parseInt(hours, 10);
50+
const ampm = hoursNum >= 12 ? "PM" : "AM";
51+
const formattedHours = hoursNum % 12 || 12; // Convert 0 to 12 for 12 AM
52+
const formattedTime = `${String(formattedHours).padStart(2, '0')}:${minutes} ${ampm}`;
53+
4454
onAddEntry({
4555
type,
4656
title,
4757
description,
48-
time,
58+
time: formattedTime,
4959
status: "upcoming",
5060
provider: provider || undefined,
5161
date: date,
52-
recurring: recurring,
62+
recurring: recurring ? recurrence : undefined,
5363
});
5464

5565
// Reset form
@@ -59,13 +69,14 @@ export function AddEntryDialog({ onAddEntry }: AddEntryDialogProps) {
5969
setTime("");
6070
setProvider("");
6171
setRecurring(false);
72+
setRecurrence('daily');
6273
setOpen(false);
6374
};
6475

6576
return (
6677
<Dialog open={open} onOpenChange={setOpen}>
6778
<DialogTrigger asChild>
68-
<Button size="lg" className="gap-2">
79+
<Button size="lg" className={cn("gap-2", buttonClassName)}>
6980
<Plus className="h-5 w-5" />
7081
Add Entry
7182
</Button>
@@ -80,7 +91,7 @@ export function AddEntryDialog({ onAddEntry }: AddEntryDialogProps) {
8091
<form onSubmit={handleSubmit} className="space-y-4">
8192
<div className="space-y-2">
8293
<Label htmlFor="type">Entry Type</Label>
83-
<Select value={type} onValueChange={(value) => setType(value as EntryType)}>
94+
<Select value={type} onValueChange={(value) => setType(value as TimelineEntryData["type"])}>
8495
<SelectTrigger id="type">
8596
<SelectValue />
8697
</SelectTrigger>
@@ -149,18 +160,28 @@ export function AddEntryDialog({ onAddEntry }: AddEntryDialogProps) {
149160
/>
150161
</div>
151162

152-
<div className="flex items-center space-x-2">
153-
<Checkbox
154-
id="recurring"
155-
checked={recurring}
156-
onCheckedChange={(checked) => setRecurring(checked as boolean)}
157-
/>
158-
<Label
159-
htmlFor="recurring"
160-
className="text-sm font-normal cursor-pointer"
161-
>
162-
This is a recurring event
163-
</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+
)}
164185
</div>
165186

166187
<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 border-b bg-medical-blue 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", 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+
}

0 commit comments

Comments
 (0)