Add 1 files
Browse files- index.html +946 -288
index.html
CHANGED
@@ -3,400 +3,693 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
-
<title>MedTrack - Medication Reminder</title>
|
7 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
8 |
<style>
|
9 |
:root {
|
10 |
-
--primary: #
|
11 |
-
--
|
12 |
-
--
|
13 |
-
--
|
14 |
-
--
|
15 |
-
--
|
16 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
}
|
18 |
|
19 |
* {
|
20 |
margin: 0;
|
21 |
padding: 0;
|
22 |
box-sizing: border-box;
|
23 |
-
font-family: '
|
24 |
}
|
25 |
|
26 |
body {
|
27 |
-
background
|
28 |
color: var(--dark);
|
29 |
line-height: 1.6;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
|
32 |
.container {
|
33 |
-
max-width:
|
34 |
margin: 0 auto;
|
35 |
-
padding:
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
}
|
37 |
|
38 |
header {
|
39 |
display: flex;
|
40 |
justify-content: space-between;
|
41 |
align-items: center;
|
42 |
-
margin-bottom:
|
43 |
-
padding-bottom:
|
44 |
-
border-bottom: 1px solid
|
45 |
}
|
46 |
|
47 |
.logo {
|
48 |
display: flex;
|
49 |
align-items: center;
|
50 |
-
gap:
|
51 |
}
|
52 |
|
53 |
-
.logo
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
}
|
57 |
|
58 |
-
.logo
|
|
|
59 |
font-size: 24px;
|
60 |
-
font-weight: 600;
|
61 |
-
color: var(--dark);
|
62 |
}
|
63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
.btn {
|
65 |
-
padding:
|
66 |
border: none;
|
67 |
-
border-radius:
|
68 |
cursor: pointer;
|
69 |
-
font-weight:
|
70 |
-
transition: all 0.3s
|
71 |
display: inline-flex;
|
72 |
align-items: center;
|
73 |
-
gap:
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
}
|
75 |
|
76 |
.btn-primary {
|
77 |
-
background
|
78 |
-
color: white;
|
79 |
}
|
80 |
|
81 |
.btn-primary:hover {
|
82 |
-
|
|
|
83 |
}
|
84 |
|
85 |
.btn-secondary {
|
86 |
-
background
|
87 |
-
color: white;
|
88 |
}
|
89 |
|
90 |
.btn-secondary:hover {
|
91 |
-
|
|
|
92 |
}
|
93 |
|
94 |
.btn-success {
|
95 |
-
background
|
96 |
-
color: white;
|
97 |
}
|
98 |
|
99 |
.btn-success:hover {
|
100 |
-
|
|
|
101 |
}
|
102 |
|
103 |
.btn-danger {
|
104 |
-
background
|
105 |
-
color: white;
|
106 |
}
|
107 |
|
108 |
.btn-danger:hover {
|
109 |
-
|
|
|
110 |
}
|
111 |
|
112 |
.btn-outline {
|
113 |
background-color: transparent;
|
114 |
-
border:
|
115 |
color: var(--dark);
|
|
|
116 |
}
|
117 |
|
118 |
.btn-outline:hover {
|
119 |
-
background-color: var(--
|
|
|
120 |
}
|
121 |
|
122 |
/* Dashboard Section */
|
123 |
.dashboard {
|
124 |
-
margin-bottom:
|
125 |
}
|
126 |
|
127 |
.stats {
|
128 |
display: grid;
|
129 |
grid-template-columns: repeat(3, 1fr);
|
130 |
-
gap:
|
131 |
-
margin-bottom:
|
132 |
}
|
133 |
|
134 |
.stat-card {
|
135 |
-
background
|
136 |
-
|
137 |
-
border
|
138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
}
|
140 |
|
141 |
.stat-card h3 {
|
142 |
font-size: 14px;
|
143 |
-
|
144 |
-
|
|
|
145 |
}
|
146 |
|
147 |
.stat-card p {
|
148 |
-
font-size:
|
149 |
-
font-weight:
|
150 |
}
|
151 |
|
152 |
/* Upcoming Medications */
|
153 |
.upcoming {
|
154 |
-
background
|
155 |
-
|
156 |
-
|
157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
}
|
159 |
|
160 |
.section-title {
|
161 |
display: flex;
|
162 |
justify-content: space-between;
|
163 |
align-items: center;
|
164 |
-
margin-bottom:
|
165 |
}
|
166 |
|
167 |
.section-title h2 {
|
168 |
-
font-size:
|
169 |
font-weight: 600;
|
170 |
-
}
|
171 |
-
|
172 |
-
.medication-list {
|
173 |
-
display: flex;
|
174 |
-
flex-direction: column;
|
175 |
-
gap: 15px;
|
176 |
-
}
|
177 |
-
|
178 |
-
.medication-item {
|
179 |
display: flex;
|
180 |
align-items: center;
|
181 |
-
|
182 |
-
border-radius: 8px;
|
183 |
-
background-color: var(--light);
|
184 |
-
transition: all 0.3s ease;
|
185 |
}
|
186 |
|
187 |
-
.
|
188 |
-
|
189 |
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
190 |
}
|
191 |
|
192 |
-
.
|
193 |
-
width: 50px;
|
194 |
-
height: 50px;
|
195 |
-
background-color: var(--secondary);
|
196 |
-
border-radius: 50%;
|
197 |
display: flex;
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
.
|
210 |
-
|
211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
}
|
213 |
|
214 |
-
.
|
215 |
-
|
216 |
-
color: #666;
|
217 |
}
|
218 |
|
219 |
-
.
|
220 |
-
display: flex;
|
221 |
flex-direction: column;
|
222 |
-
align
|
223 |
}
|
224 |
|
225 |
-
.med-
|
226 |
-
|
227 |
-
margin-bottom: 5px;
|
228 |
-
}
|
229 |
-
|
230 |
-
/* Modal */
|
231 |
-
.modal {
|
232 |
-
display: none;
|
233 |
-
position: fixed;
|
234 |
-
top: 0;
|
235 |
-
left: 0;
|
236 |
-
width: 100%;
|
237 |
-
height: 100%;
|
238 |
-
background-color: rgba(0, 0, 0, 0.5);
|
239 |
-
z-index: 1000;
|
240 |
-
justify-content: center;
|
241 |
-
align-items: center;
|
242 |
-
}
|
243 |
-
|
244 |
-
.modal-content {
|
245 |
-
background-color: white;
|
246 |
-
padding: 25px;
|
247 |
-
border-radius: 10px;
|
248 |
-
width: 90%;
|
249 |
-
max-width: 500px;
|
250 |
-
max-height: 90vh;
|
251 |
-
overflow-y: auto;
|
252 |
-
}
|
253 |
-
|
254 |
-
.modal-header {
|
255 |
-
display: flex;
|
256 |
-
justify-content: space-between;
|
257 |
-
align-items: center;
|
258 |
-
margin-bottom: 20px;
|
259 |
-
}
|
260 |
-
|
261 |
-
.modal-header h2 {
|
262 |
-
font-size: 20px;
|
263 |
-
}
|
264 |
-
|
265 |
-
.close-btn {
|
266 |
-
background: none;
|
267 |
-
border: none;
|
268 |
-
font-size: 24px;
|
269 |
-
cursor: pointer;
|
270 |
-
color: #666;
|
271 |
-
}
|
272 |
-
|
273 |
-
.form-group {
|
274 |
margin-bottom: 20px;
|
275 |
}
|
276 |
|
277 |
-
.
|
278 |
-
display: block;
|
279 |
-
margin-bottom: 8px;
|
280 |
-
font-weight: 500;
|
281 |
-
}
|
282 |
-
|
283 |
-
.form-control {
|
284 |
-
width: 100%;
|
285 |
-
padding: 10px 15px;
|
286 |
-
border: 1px solid var(--gray);
|
287 |
-
border-radius: 5px;
|
288 |
-
font-size: 16px;
|
289 |
-
}
|
290 |
-
|
291 |
-
.form-control:focus {
|
292 |
-
outline: none;
|
293 |
-
border-color: var(--primary);
|
294 |
-
}
|
295 |
-
|
296 |
-
.time-inputs {
|
297 |
-
display: grid;
|
298 |
-
grid-template-columns: 1fr 1fr;
|
299 |
-
gap: 15px;
|
300 |
-
}
|
301 |
-
|
302 |
-
.modal-footer {
|
303 |
-
display: flex;
|
304 |
-
justify-content: flex-end;
|
305 |
-
gap: 10px;
|
306 |
-
margin-top: 20px;
|
307 |
-
}
|
308 |
-
|
309 |
-
/* Notification */
|
310 |
-
.notification {
|
311 |
-
position: fixed;
|
312 |
-
bottom: 20px;
|
313 |
-
right: 20px;
|
314 |
-
background-color: var(--primary);
|
315 |
-
color: white;
|
316 |
-
padding: 15px 25px;
|
317 |
-
border-radius: 8px;
|
318 |
-
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
319 |
-
display: flex;
|
320 |
align-items: center;
|
321 |
-
|
322 |
-
transform: translateY(100px);
|
323 |
-
opacity: 0;
|
324 |
-
transition: all 0.3s ease;
|
325 |
-
z-index: 100;
|
326 |
-
}
|
327 |
-
|
328 |
-
.notification.show {
|
329 |
-
transform: translateY(0);
|
330 |
-
opacity: 1;
|
331 |
-
}
|
332 |
-
|
333 |
-
.notification i {
|
334 |
-
font-size: 20px;
|
335 |
}
|
336 |
|
337 |
-
|
338 |
-
|
339 |
-
.stats {
|
340 |
-
grid-template-columns: 1fr;
|
341 |
-
}
|
342 |
-
|
343 |
-
.medication-item {
|
344 |
-
flex-direction: column;
|
345 |
-
text-align: center;
|
346 |
-
}
|
347 |
-
|
348 |
-
.med-icon {
|
349 |
-
margin-right: 0;
|
350 |
-
margin-bottom: 15px;
|
351 |
-
}
|
352 |
-
|
353 |
-
.med-time {
|
354 |
-
align-items: center;
|
355 |
-
margin-top: 10px;
|
356 |
-
}
|
357 |
}
|
358 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
359 |
</head>
|
360 |
<body>
|
|
|
|
|
|
|
|
|
|
|
361 |
<div class="container">
|
362 |
<header>
|
363 |
<div class="logo">
|
364 |
-
<
|
365 |
-
|
|
|
|
|
|
|
|
|
|
|
366 |
</div>
|
367 |
<button class="btn btn-primary" id="addMedBtn">
|
368 |
<i class="fas fa-plus"></i> Add Medication
|
369 |
</button>
|
370 |
</header>
|
371 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
<section class="dashboard">
|
373 |
<div class="stats">
|
374 |
-
<div class="stat-card">
|
375 |
-
<h3>Upcoming Today</h3>
|
376 |
<p id="upcomingCount">3</p>
|
377 |
</div>
|
378 |
-
<div class="stat-card">
|
379 |
-
<h3>Medications</h3>
|
380 |
<p id="totalMeds">5</p>
|
381 |
</div>
|
382 |
-
<div class="stat-card">
|
383 |
-
<h3>Adherence</h3>
|
384 |
<p id="adherenceRate">92%</p>
|
385 |
</div>
|
386 |
</div>
|
387 |
</section>
|
388 |
|
389 |
-
<section class="upcoming">
|
390 |
<div class="section-title">
|
391 |
<h2><i class="far fa-clock"></i> Upcoming Medications</h2>
|
392 |
-
<
|
393 |
-
<
|
394 |
-
|
|
|
|
|
395 |
</div>
|
396 |
|
397 |
<div class="medication-list" id="medicationList">
|
398 |
<!-- Medication items will be added here dynamically -->
|
399 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
</section>
|
401 |
</div>
|
402 |
|
@@ -404,20 +697,20 @@
|
|
404 |
<div class="modal" id="addMedModal">
|
405 |
<div class="modal-content">
|
406 |
<div class="modal-header">
|
407 |
-
<h2>Add New Medication</h2>
|
408 |
<button class="close-btn" id="closeModal">×</button>
|
409 |
</div>
|
410 |
<form id="medicationForm">
|
411 |
<div class="form-group">
|
412 |
-
<label for="medName">Medication Name</label>
|
413 |
<input type="text" id="medName" class="form-control" placeholder="e.g. Ibuprofen" required>
|
414 |
</div>
|
415 |
<div class="form-group">
|
416 |
-
<label for="medDosage">Dosage</label>
|
417 |
<input type="text" id="medDosage" class="form-control" placeholder="e.g. 200mg" required>
|
418 |
</div>
|
419 |
<div class="form-group">
|
420 |
-
<label for="medFrequency">Frequency</label>
|
421 |
<select id="medFrequency" class="form-control" required>
|
422 |
<option value="">Select frequency</option>
|
423 |
<option value="once">Once daily</option>
|
@@ -427,7 +720,7 @@
|
|
427 |
</select>
|
428 |
</div>
|
429 |
<div class="form-group">
|
430 |
-
<label>Times</label>
|
431 |
<div class="time-inputs" id="timeInputs">
|
432 |
<div>
|
433 |
<input type="time" id="time1" class="form-control" required>
|
@@ -435,7 +728,7 @@
|
|
435 |
</div>
|
436 |
</div>
|
437 |
<div class="form-group">
|
438 |
-
<label for="medNotes">Notes (Optional)</label>
|
439 |
<textarea id="medNotes" class="form-control" rows="3" placeholder="Special instructions..."></textarea>
|
440 |
</div>
|
441 |
<div class="modal-footer">
|
@@ -447,13 +740,13 @@
|
|
447 |
</div>
|
448 |
|
449 |
<!-- Notification -->
|
450 |
-
<div class="notification" id="notification">
|
451 |
<i class="fas fa-check-circle"></i>
|
452 |
<span id="notificationText">Medication added successfully!</span>
|
453 |
</div>
|
454 |
|
455 |
<script>
|
456 |
-
//
|
457 |
const sampleMedications = [
|
458 |
{
|
459 |
id: 1,
|
@@ -461,7 +754,9 @@
|
|
461 |
dosage: "200mg",
|
462 |
time: "08:00",
|
463 |
isTaken: false,
|
464 |
-
note: "Take with
|
|
|
|
|
465 |
},
|
466 |
{
|
467 |
id: 2,
|
@@ -469,7 +764,9 @@
|
|
469 |
dosage: "1000IU",
|
470 |
time: "12:00",
|
471 |
isTaken: false,
|
472 |
-
note: "With lunch"
|
|
|
|
|
473 |
},
|
474 |
{
|
475 |
id: 3,
|
@@ -477,13 +774,36 @@
|
|
477 |
dosage: "1 tablet",
|
478 |
time: "18:00",
|
479 |
isTaken: false,
|
480 |
-
note: ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
481 |
}
|
482 |
];
|
483 |
|
484 |
// DOM Elements
|
485 |
const medicationList = document.getElementById('medicationList');
|
486 |
const addMedBtn = document.getElementById('addMedBtn');
|
|
|
487 |
const addMedModal = document.getElementById('addMedModal');
|
488 |
const closeModal = document.getElementById('closeModal');
|
489 |
const cancelBtn = document.getElementById('cancelBtn');
|
@@ -495,18 +815,197 @@
|
|
495 |
const upcomingCount = document.getElementById('upcomingCount');
|
496 |
const totalMeds = document.getElementById('totalMeds');
|
497 |
const adherenceRate = document.getElementById('adherenceRate');
|
|
|
|
|
|
|
|
|
|
|
498 |
|
499 |
// Current medications
|
500 |
let medications = [...sampleMedications];
|
501 |
-
let nextId =
|
|
|
|
|
|
|
502 |
|
503 |
// Initialize the app
|
504 |
function init() {
|
505 |
renderMedications();
|
506 |
updateStats();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
507 |
checkNotifications();
|
508 |
}
|
509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
510 |
// Render medications to the list
|
511 |
function renderMedications() {
|
512 |
medicationList.innerHTML = '';
|
@@ -516,18 +1015,43 @@
|
|
516 |
return a.time.localeCompare(b.time);
|
517 |
});
|
518 |
|
519 |
-
sortedMeds.
|
520 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
const medItem = document.createElement('div');
|
522 |
-
medItem.className = 'medication-item';
|
523 |
medItem.dataset.id = med.id;
|
|
|
524 |
|
525 |
const [hours, minutes] = med.time.split(':');
|
526 |
-
let timeDisplay = med.time;
|
527 |
const hour = parseInt(hours);
|
528 |
const amPm = hour >= 12 ? 'PM' : 'AM';
|
529 |
const hour12 = hour % 12 || 12;
|
530 |
-
timeDisplay = `${hour12}:${minutes} ${amPm}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
531 |
|
532 |
medItem.innerHTML = `
|
533 |
<div class="med-icon">
|
@@ -535,22 +1059,42 @@
|
|
535 |
</div>
|
536 |
<div class="med-details">
|
537 |
<h3>${med.name}</h3>
|
538 |
-
<p>${med.dosage} ${med.
|
|
|
539 |
</div>
|
540 |
-
<div class="med-time">
|
541 |
-
<p>${timeDisplay}</p>
|
542 |
-
<button class="btn btn-success
|
543 |
-
<i class="fas fa-check"></i> Taken
|
544 |
</button>
|
545 |
</div>
|
546 |
`;
|
547 |
|
|
|
|
|
|
|
|
|
548 |
medicationList.appendChild(medItem);
|
549 |
|
550 |
// Add event listener to the take button
|
551 |
medItem.querySelector('.take-btn').addEventListener('click', () => takeMedication(med.id));
|
552 |
-
}
|
553 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
554 |
}
|
555 |
|
556 |
// Mark medication as taken
|
@@ -558,23 +1102,48 @@
|
|
558 |
const index = medications.findIndex(med => med.id === id);
|
559 |
if (index !== -1) {
|
560 |
medications[index].isTaken = true;
|
|
|
561 |
renderMedications();
|
562 |
updateStats();
|
563 |
-
showNotification(`Marked ${medications[index].name} as taken
|
|
|
|
|
|
|
|
|
|
|
|
|
564 |
|
565 |
-
// Schedule notification for next dose
|
566 |
-
|
|
|
|
|
567 |
}
|
568 |
}
|
569 |
|
570 |
-
// Show notification
|
571 |
-
function showNotification(message) {
|
|
|
|
|
572 |
notificationText.textContent = message;
|
573 |
notification.classList.add('show');
|
574 |
|
575 |
setTimeout(() => {
|
576 |
notification.classList.remove('show');
|
577 |
}, 3000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
578 |
}
|
579 |
|
580 |
// Check if any medications are due
|
@@ -586,7 +1155,25 @@
|
|
586 |
|
587 |
medications.forEach(med => {
|
588 |
if (!med.isTaken && med.time === currentTime) {
|
589 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
590 |
}
|
591 |
});
|
592 |
}
|
@@ -594,39 +1181,65 @@
|
|
594 |
// Schedule next notification for recurring medications
|
595 |
function scheduleNextNotification(medication) {
|
596 |
// In a real app, this would schedule an actual notification
|
597 |
-
|
|
|
|
|
598 |
}
|
599 |
|
600 |
-
// Update statistics
|
601 |
function updateStats() {
|
602 |
const total = medications.length;
|
603 |
const upcoming = medications.filter(med => !med.isTaken).length;
|
604 |
const taken = medications.filter(med => med.isTaken).length;
|
605 |
const adherence = total > 0 ? Math.round((taken / total) * 100) : 0;
|
606 |
|
607 |
-
|
608 |
-
|
609 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
610 |
}
|
611 |
|
612 |
-
// Show modal
|
613 |
function showModal() {
|
614 |
addMedModal.style.display = 'flex';
|
615 |
document.body.style.overflow = 'hidden';
|
|
|
616 |
}
|
617 |
|
618 |
-
// Hide modal
|
619 |
function hideModal() {
|
620 |
-
addMedModal.
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
|
|
|
|
|
|
626 |
}
|
627 |
|
628 |
-
// Add new medication
|
629 |
function addMedication(name, dosage, frequency, times, note) {
|
|
|
|
|
|
|
630 |
times.forEach(time => {
|
631 |
const newMed = {
|
632 |
id: nextId++,
|
@@ -634,20 +1247,43 @@
|
|
634 |
dosage,
|
635 |
time,
|
636 |
isTaken: false,
|
637 |
-
note
|
|
|
|
|
638 |
};
|
639 |
|
640 |
medications.push(newMed);
|
|
|
|
|
|
|
|
|
|
|
641 |
});
|
642 |
|
643 |
renderMedications();
|
644 |
updateStats();
|
645 |
-
showNotification(`Added ${name} to your medications
|
646 |
hideModal();
|
647 |
}
|
648 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
649 |
// Event Listeners
|
650 |
addMedBtn.addEventListener('click', showModal);
|
|
|
651 |
closeModal.addEventListener('click', hideModal);
|
652 |
cancelBtn.addEventListener('click', hideModal);
|
653 |
|
@@ -687,18 +1323,40 @@
|
|
687 |
});
|
688 |
|
689 |
if (times.length === 0) {
|
690 |
-
|
691 |
return;
|
692 |
}
|
693 |
|
694 |
addMedication(name, dosage, frequency, times, note);
|
695 |
});
|
696 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
697 |
// Initialize the app
|
698 |
init();
|
699 |
|
700 |
-
//
|
701 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
702 |
</script>
|
703 |
-
|
704 |
</html>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>MedTrack - Premium Medication Reminder</title>
|
7 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
8 |
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
9 |
<style>
|
10 |
:root {
|
11 |
+
--primary: #6c5ce7;
|
12 |
+
--primary-light: #a29bfe;
|
13 |
+
--primary-dark: #5649c2;
|
14 |
+
--secondary: #00cec9;
|
15 |
+
--success: #00b894;
|
16 |
+
--danger: #d63031;
|
17 |
+
--warning: #fdcb6e;
|
18 |
+
--light: #f5f6fa;
|
19 |
+
--dark: #2d3436;
|
20 |
+
--gray: #636e72;
|
21 |
+
--gray-light: #dfe6e9;
|
22 |
+
--white: #ffffff;
|
23 |
+
--glass: rgba(255, 255, 255, 0.25);
|
24 |
+
--glass-border: rgba(255, 255, 255, 0.4);
|
25 |
}
|
26 |
|
27 |
* {
|
28 |
margin: 0;
|
29 |
padding: 0;
|
30 |
box-sizing: border-box;
|
31 |
+
font-family: 'Poppins', sans-serif;
|
32 |
}
|
33 |
|
34 |
body {
|
35 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
36 |
color: var(--dark);
|
37 |
line-height: 1.6;
|
38 |
+
min-height: 100vh;
|
39 |
+
}
|
40 |
+
|
41 |
+
/* Floating background elements */
|
42 |
+
.bg-shapes {
|
43 |
+
position: fixed;
|
44 |
+
top: 0;
|
45 |
+
left: 0;
|
46 |
+
width: 100%;
|
47 |
+
height: 100%;
|
48 |
+
overflow: hidden;
|
49 |
+
z-index: -1;
|
50 |
+
}
|
51 |
+
|
52 |
+
.bg-shape {
|
53 |
+
position: absolute;
|
54 |
+
border-radius: 50%;
|
55 |
+
opacity: 0.1;
|
56 |
+
filter: blur(50px);
|
57 |
+
}
|
58 |
+
|
59 |
+
.shape-1 {
|
60 |
+
width: 400px;
|
61 |
+
height: 400px;
|
62 |
+
background: var(--primary);
|
63 |
+
top: -100px;
|
64 |
+
left: -100px;
|
65 |
+
}
|
66 |
+
|
67 |
+
.shape-2 {
|
68 |
+
width: 600px;
|
69 |
+
height: 600px;
|
70 |
+
background: var(--secondary);
|
71 |
+
bottom: -200px;
|
72 |
+
right: -200px;
|
73 |
}
|
74 |
|
75 |
.container {
|
76 |
+
max-width: 900px;
|
77 |
margin: 0 auto;
|
78 |
+
padding: 30px;
|
79 |
+
animation: fadeIn 0.8s ease-out;
|
80 |
+
}
|
81 |
+
|
82 |
+
@keyframes fadeIn {
|
83 |
+
from { opacity: 0; transform: translateY(20px); }
|
84 |
+
to { opacity: 1; transform: translateY(0); }
|
85 |
}
|
86 |
|
87 |
header {
|
88 |
display: flex;
|
89 |
justify-content: space-between;
|
90 |
align-items: center;
|
91 |
+
margin-bottom: 40px;
|
92 |
+
padding-bottom: 20px;
|
93 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
|
94 |
}
|
95 |
|
96 |
.logo {
|
97 |
display: flex;
|
98 |
align-items: center;
|
99 |
+
gap: 15px;
|
100 |
}
|
101 |
|
102 |
+
.logo-icon {
|
103 |
+
width: 50px;
|
104 |
+
height: 50px;
|
105 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
106 |
+
border-radius: 12px;
|
107 |
+
display: flex;
|
108 |
+
align-items: center;
|
109 |
+
justify-content: center;
|
110 |
+
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.3);
|
111 |
}
|
112 |
|
113 |
+
.logo-icon i {
|
114 |
+
color: var(--white);
|
115 |
font-size: 24px;
|
|
|
|
|
116 |
}
|
117 |
|
118 |
+
.logo-text h1 {
|
119 |
+
font-size: 28px;
|
120 |
+
font-weight: 700;
|
121 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
122 |
+
-webkit-background-clip: text;
|
123 |
+
-webkit-text-fill-color: transparent;
|
124 |
+
margin-bottom: 3px;
|
125 |
+
}
|
126 |
+
|
127 |
+
.logo-text p {
|
128 |
+
font-size: 12px;
|
129 |
+
color: var(--gray);
|
130 |
+
letter-spacing: 1px;
|
131 |
+
text-transform: uppercase;
|
132 |
+
}
|
133 |
+
|
134 |
+
/* Buttons */
|
135 |
.btn {
|
136 |
+
padding: 12px 20px;
|
137 |
border: none;
|
138 |
+
border-radius: 12px;
|
139 |
cursor: pointer;
|
140 |
+
font-weight: 600;
|
141 |
+
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
142 |
display: inline-flex;
|
143 |
align-items: center;
|
144 |
+
gap: 10px;
|
145 |
+
font-size: 14px;
|
146 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
147 |
+
}
|
148 |
+
|
149 |
+
.btn i {
|
150 |
+
font-size: 16px;
|
151 |
}
|
152 |
|
153 |
.btn-primary {
|
154 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
155 |
+
color: var(--white);
|
156 |
}
|
157 |
|
158 |
.btn-primary:hover {
|
159 |
+
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.4);
|
160 |
+
transform: translateY(-2px);
|
161 |
}
|
162 |
|
163 |
.btn-secondary {
|
164 |
+
background: linear-gradient(135deg, var(--secondary) 0%, #00a6b3 100%);
|
165 |
+
color: var(--white);
|
166 |
}
|
167 |
|
168 |
.btn-secondary:hover {
|
169 |
+
box-shadow: 0 8px 20px rgba(0, 206, 201, 0.4);
|
170 |
+
transform: translateY(-2px);
|
171 |
}
|
172 |
|
173 |
.btn-success {
|
174 |
+
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%);
|
175 |
+
color: var(--white);
|
176 |
}
|
177 |
|
178 |
.btn-success:hover {
|
179 |
+
box-shadow: 0 8px 20px rgba(0, 184, 148, 0.4);
|
180 |
+
transform: translateY(-2px);
|
181 |
}
|
182 |
|
183 |
.btn-danger {
|
184 |
+
background: linear-gradient(135deg, var(--danger) 0%, #c02929 100%);
|
185 |
+
color: var(--white);
|
186 |
}
|
187 |
|
188 |
.btn-danger:hover {
|
189 |
+
box-shadow: 0 8px 20px rgba(214, 48, 49, 0.4);
|
190 |
+
transform: translateY(-2px);
|
191 |
}
|
192 |
|
193 |
.btn-outline {
|
194 |
background-color: transparent;
|
195 |
+
border: 2px solid var(--glass-border);
|
196 |
color: var(--dark);
|
197 |
+
backdrop-filter: blur(5px);
|
198 |
}
|
199 |
|
200 |
.btn-outline:hover {
|
201 |
+
background-color: var(--glass);
|
202 |
+
transform: translateY(-2px);
|
203 |
}
|
204 |
|
205 |
/* Dashboard Section */
|
206 |
.dashboard {
|
207 |
+
margin-bottom: 40px;
|
208 |
}
|
209 |
|
210 |
.stats {
|
211 |
display: grid;
|
212 |
grid-template-columns: repeat(3, 1fr);
|
213 |
+
gap: 20px;
|
214 |
+
margin-bottom: 30px;
|
215 |
}
|
216 |
|
217 |
.stat-card {
|
218 |
+
background: var(--glass);
|
219 |
+
backdrop-filter: blur(10px);
|
220 |
+
border: 1px solid var(--glass-border);
|
221 |
+
padding: 25px;
|
222 |
+
border-radius: 16px;
|
223 |
+
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
|
224 |
+
transition: all 0.3s ease;
|
225 |
+
}
|
226 |
+
|
227 |
+
.stat-card:hover {
|
228 |
+
transform: translateY(-5px);
|
229 |
+
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.2);
|
230 |
}
|
231 |
|
232 |
.stat-card h3 {
|
233 |
font-size: 14px;
|
234 |
+
font-weight: 500;
|
235 |
+
color: var(--gray);
|
236 |
+
margin-bottom: 10px;
|
237 |
}
|
238 |
|
239 |
.stat-card p {
|
240 |
+
font-size: 28px;
|
241 |
+
font-weight: 700;
|
242 |
}
|
243 |
|
244 |
/* Upcoming Medications */
|
245 |
.upcoming {
|
246 |
+
background: var(--glass);
|
247 |
+
backdrop-filter: blur(10px);
|
248 |
+
border: 1px solid var(--glass-border);
|
249 |
+
border-radius: 16px;
|
250 |
+
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
|
251 |
+
padding: 25px;
|
252 |
+
transition: all 0.3s ease;
|
253 |
+
}
|
254 |
+
|
255 |
+
.upcoming:hover {
|
256 |
+
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.2);
|
257 |
}
|
258 |
|
259 |
.section-title {
|
260 |
display: flex;
|
261 |
justify-content: space-between;
|
262 |
align-items: center;
|
263 |
+
margin-bottom: 25px;
|
264 |
}
|
265 |
|
266 |
.section-title h2 {
|
267 |
+
font-size: 20px;
|
268 |
font-weight: 600;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
display: flex;
|
270 |
align-items: center;
|
271 |
+
gap: 10px;
|
|
|
|
|
|
|
272 |
}
|
273 |
|
274 |
+
.section-title i {
|
275 |
+
color: var(--primary);
|
|
|
276 |
}
|
277 |
|
278 |
+
.medication-list {
|
|
|
|
|
|
|
|
|
279 |
display: flex;
|
280 |
+
flex-direction: column;
|
281 |
+
gap: 15px;
|
282 |
+
}
|
283 |
+
|
284 |
+
.medication-item {
|
285 |
+
display: flex;
|
286 |
+
align-items: center;
|
287 |
+
padding: 20px;
|
288 |
+
border-radius: 12px;
|
289 |
+
background: var(--white);
|
290 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
291 |
+
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
292 |
+
position: relative;
|
293 |
+
overflow: hidden;
|
294 |
+
}
|
295 |
+
|
296 |
+
.medication-item:hover {
|
297 |
+
transform: translateY(-3px);
|
298 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
299 |
+
}
|
300 |
+
|
301 |
+
.medication-item::before {
|
302 |
+
content: '';
|
303 |
+
position: absolute;
|
304 |
+
top: 0;
|
305 |
+
left: 0;
|
306 |
+
width: 5px;
|
307 |
+
height: 100%;
|
308 |
+
background: linear-gradient(to bottom, var(--primary), var(--secondary));
|
309 |
+
}
|
310 |
+
|
311 |
+
.med-icon {
|
312 |
+
width: 55px;
|
313 |
+
height: 55px;
|
314 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
315 |
+
border-radius: 12px;
|
316 |
+
display: flex;
|
317 |
+
align-items: center;
|
318 |
+
justify-content: center;
|
319 |
+
margin-right: 20px;
|
320 |
+
color: var(--white);
|
321 |
+
font-size: 22px;
|
322 |
+
box-shadow: 0 4px 15px rgba(108, 92, 231, 0.3);
|
323 |
+
}
|
324 |
+
|
325 |
+
.med-details {
|
326 |
+
flex: 1;
|
327 |
+
}
|
328 |
+
|
329 |
+
.med-details h3 {
|
330 |
+
font-size: 18px;
|
331 |
+
margin-bottom: 8px;
|
332 |
+
font-weight: 600;
|
333 |
+
}
|
334 |
+
|
335 |
+
.med-details p {
|
336 |
+
font-size: 14px;
|
337 |
+
color: var(--gray);
|
338 |
+
}
|
339 |
+
|
340 |
+
.med-note {
|
341 |
+
font-size: 13px;
|
342 |
+
color: var(--gray);
|
343 |
+
margin-top: 5px;
|
344 |
+
display: flex;
|
345 |
+
align-items: center;
|
346 |
+
gap: 5px;
|
347 |
+
}
|
348 |
+
|
349 |
+
.med-time {
|
350 |
+
display: flex;
|
351 |
+
flex-direction: column;
|
352 |
+
align-items: flex-end;
|
353 |
+
}
|
354 |
+
|
355 |
+
.med-time p {
|
356 |
+
font-weight: 600;
|
357 |
+
margin-bottom: 10px;
|
358 |
+
font-size: 15px;
|
359 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
360 |
+
-webkit-background-clip: text;
|
361 |
+
-webkit-text-fill-color: transparent;
|
362 |
+
}
|
363 |
+
|
364 |
+
/* Modal */
|
365 |
+
.modal {
|
366 |
+
display: none;
|
367 |
+
position: fixed;
|
368 |
+
top: 0;
|
369 |
+
left: 0;
|
370 |
+
width: 100%;
|
371 |
+
height: 100%;
|
372 |
+
background-color: rgba(45, 52, 54, 0.8);
|
373 |
+
z-index: 1000;
|
374 |
+
justify-content: center;
|
375 |
+
align-items: center;
|
376 |
+
backdrop-filter: blur(5px);
|
377 |
+
animation: fadeIn 0.3s ease-out;
|
378 |
+
}
|
379 |
+
|
380 |
+
.modal-content {
|
381 |
+
background: var(--white);
|
382 |
+
padding: 30px;
|
383 |
+
border-radius: 20px;
|
384 |
+
width: 90%;
|
385 |
+
max-width: 500px;
|
386 |
+
max-height: 90vh;
|
387 |
+
overflow-y: auto;
|
388 |
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
|
389 |
+
transform: translateY(0);
|
390 |
+
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
391 |
+
}
|
392 |
+
|
393 |
+
.modal.show .modal-content {
|
394 |
+
animation: modalSlideIn 0.4s ease-out;
|
395 |
+
}
|
396 |
+
|
397 |
+
@keyframes modalSlideIn {
|
398 |
+
from { transform: translateY(50px); opacity: 0; }
|
399 |
+
to { transform: translateY(0); opacity: 1; }
|
400 |
+
}
|
401 |
+
|
402 |
+
.modal-header {
|
403 |
+
display: flex;
|
404 |
+
justify-content: space-between;
|
405 |
+
align-items: center;
|
406 |
+
margin-bottom: 25px;
|
407 |
+
}
|
408 |
+
|
409 |
+
.modal-header h2 {
|
410 |
+
font-size: 24px;
|
411 |
+
font-weight: 600;
|
412 |
+
color: var(--primary);
|
413 |
+
}
|
414 |
+
|
415 |
+
.close-btn {
|
416 |
+
background: none;
|
417 |
+
border: none;
|
418 |
+
font-size: 28px;
|
419 |
+
cursor: pointer;
|
420 |
+
color: var(--gray);
|
421 |
+
transition: all 0.3s ease;
|
422 |
+
}
|
423 |
+
|
424 |
+
.close-btn:hover {
|
425 |
+
color: var(--danger);
|
426 |
+
transform: rotate(90deg);
|
427 |
+
}
|
428 |
+
|
429 |
+
.form-group {
|
430 |
+
margin-bottom: 25px;
|
431 |
+
}
|
432 |
+
|
433 |
+
.form-group label {
|
434 |
+
display: block;
|
435 |
+
margin-bottom: 10px;
|
436 |
+
font-weight: 500;
|
437 |
+
color: var(--dark);
|
438 |
+
}
|
439 |
+
|
440 |
+
.form-control {
|
441 |
+
width: 100%;
|
442 |
+
padding: 14px 20px;
|
443 |
+
border: 2px solid var(--gray-light);
|
444 |
+
border-radius: 12px;
|
445 |
+
font-size: 16px;
|
446 |
+
transition: all 0.3s ease;
|
447 |
+
background-color: var(--light);
|
448 |
+
}
|
449 |
+
|
450 |
+
.form-control:focus {
|
451 |
+
outline: none;
|
452 |
+
border-color: var(--primary);
|
453 |
+
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2);
|
454 |
+
}
|
455 |
+
|
456 |
+
.time-inputs {
|
457 |
+
display: grid;
|
458 |
+
grid-template-columns: 1fr 1fr;
|
459 |
+
gap: 15px;
|
460 |
+
}
|
461 |
+
|
462 |
+
textarea.form-control {
|
463 |
+
min-height: 100px;
|
464 |
+
resize: vertical;
|
465 |
+
}
|
466 |
+
|
467 |
+
.modal-footer {
|
468 |
+
display: flex;
|
469 |
+
justify-content: flex-end;
|
470 |
+
gap: 15px;
|
471 |
+
margin-top: 30px;
|
472 |
+
}
|
473 |
+
|
474 |
+
/* Notification */
|
475 |
+
.notification {
|
476 |
+
position: fixed;
|
477 |
+
bottom: 30px;
|
478 |
+
right: 30px;
|
479 |
+
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%);
|
480 |
+
color: var(--white);
|
481 |
+
padding: 18px 30px;
|
482 |
+
border-radius: 12px;
|
483 |
+
box-shadow: 0 10px 30px rgba(0, 184, 148, 0.3);
|
484 |
+
display: flex;
|
485 |
+
align-items: center;
|
486 |
+
gap: 15px;
|
487 |
+
transform: translateX(150%);
|
488 |
+
opacity: 0;
|
489 |
+
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
490 |
+
z-index: 100;
|
491 |
+
}
|
492 |
+
|
493 |
+
.notification.show {
|
494 |
+
transform: translateX(0);
|
495 |
+
opacity: 1;
|
496 |
+
}
|
497 |
+
|
498 |
+
.notification i {
|
499 |
+
font-size: 24px;
|
500 |
+
}
|
501 |
+
|
502 |
+
.notification.success {
|
503 |
+
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%);
|
504 |
+
}
|
505 |
+
|
506 |
+
.notification.warning {
|
507 |
+
background: linear-gradient(135deg, var(--warning) 0%, #f8a84a 100%);
|
508 |
+
}
|
509 |
+
|
510 |
+
.notification.error {
|
511 |
+
background: linear-gradient(135deg, var(--danger) 0%, #c02929 100%);
|
512 |
+
}
|
513 |
+
|
514 |
+
/* Empty state */
|
515 |
+
.empty-state {
|
516 |
+
text-align: center;
|
517 |
+
padding: 40px 0;
|
518 |
+
}
|
519 |
+
|
520 |
+
.empty-state i {
|
521 |
+
font-size: 60px;
|
522 |
+
color: var(--gray-light);
|
523 |
+
margin-bottom: 20px;
|
524 |
+
}
|
525 |
+
|
526 |
+
.empty-state h3 {
|
527 |
+
font-size: 18px;
|
528 |
+
color: var(--gray);
|
529 |
+
margin-bottom: 10px;
|
530 |
+
}
|
531 |
+
|
532 |
+
.empty-state p {
|
533 |
+
font-size: 14px;
|
534 |
+
color: var(--gray-light);
|
535 |
+
margin-bottom: 20px;
|
536 |
+
}
|
537 |
+
|
538 |
+
/* Notification permissions section */
|
539 |
+
.notification-permission {
|
540 |
+
background: var(--glass);
|
541 |
+
backdrop-filter: blur(10px);
|
542 |
+
border: 1px solid var(--glass-border);
|
543 |
+
border-radius: 16px;
|
544 |
+
padding: 20px;
|
545 |
+
margin-bottom: 30px;
|
546 |
+
display: flex;
|
547 |
+
justify-content: space-between;
|
548 |
+
align-items: center;
|
549 |
+
animation: fadeIn 0.6s ease-out;
|
550 |
+
}
|
551 |
+
|
552 |
+
.notification-permission p {
|
553 |
+
font-size: 14px;
|
554 |
+
color: var(--gray);
|
555 |
+
display: flex;
|
556 |
+
align-items: center;
|
557 |
+
gap: 10px;
|
558 |
+
}
|
559 |
+
|
560 |
+
.notification-permission i {
|
561 |
+
color: var(--warning);
|
562 |
+
font-size: 18px;
|
563 |
+
}
|
564 |
+
|
565 |
+
/* Responsive */
|
566 |
+
@media (max-width: 768px) {
|
567 |
+
.container {
|
568 |
+
padding: 20px;
|
569 |
}
|
570 |
|
571 |
+
.stats {
|
572 |
+
grid-template-columns: 1fr;
|
|
|
573 |
}
|
574 |
|
575 |
+
.medication-item {
|
|
|
576 |
flex-direction: column;
|
577 |
+
text-align: center;
|
578 |
}
|
579 |
|
580 |
+
.med-icon {
|
581 |
+
margin-right: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
582 |
margin-bottom: 20px;
|
583 |
}
|
584 |
|
585 |
+
.med-time {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
586 |
align-items: center;
|
587 |
+
margin-top: 15px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
588 |
}
|
589 |
|
590 |
+
.time-inputs {
|
591 |
+
grid-template-columns: 1fr;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
592 |
}
|
593 |
+
}
|
594 |
+
|
595 |
+
/* Animation on scroll */
|
596 |
+
.animate-on-scroll {
|
597 |
+
opacity: 0;
|
598 |
+
transform: translateY(30px);
|
599 |
+
transition: all 0.6s ease-out;
|
600 |
+
}
|
601 |
+
|
602 |
+
.animate-on-scroll.show {
|
603 |
+
opacity: 1;
|
604 |
+
transform: translateY(0);
|
605 |
+
}
|
606 |
+
|
607 |
+
/* Loading spinner */
|
608 |
+
.spinner {
|
609 |
+
display: inline-block;
|
610 |
+
width: 20px;
|
611 |
+
height: 20px;
|
612 |
+
border: 3px solid rgba(255,255,255,.3);
|
613 |
+
border-radius: 50%;
|
614 |
+
border-top-color: var(--white);
|
615 |
+
animation: spin 1s ease-in-out infinite;
|
616 |
+
}
|
617 |
+
|
618 |
+
@keyframes spin {
|
619 |
+
to { transform: rotate(360deg); }
|
620 |
+
}
|
621 |
+
</style>
|
622 |
</head>
|
623 |
<body>
|
624 |
+
<div class="bg-shapes">
|
625 |
+
<div class="bg-shape shape-1"></div>
|
626 |
+
<div class="bg-shape shape-2"></div>
|
627 |
+
</div>
|
628 |
+
|
629 |
<div class="container">
|
630 |
<header>
|
631 |
<div class="logo">
|
632 |
+
<div class="logo-icon">
|
633 |
+
<i class="fas fa-pills"></i>
|
634 |
+
</div>
|
635 |
+
<div class="logo-text">
|
636 |
+
<h1>MedTrack</h1>
|
637 |
+
<p>Premium Medication Manager</p>
|
638 |
+
</div>
|
639 |
</div>
|
640 |
<button class="btn btn-primary" id="addMedBtn">
|
641 |
<i class="fas fa-plus"></i> Add Medication
|
642 |
</button>
|
643 |
</header>
|
644 |
|
645 |
+
<!-- Notification Permission Banner -->
|
646 |
+
<div class="notification-permission animate-on-scroll" id="permissionBanner" style="display: none;">
|
647 |
+
<p><i class="fas fa-bell"></i> Enable notifications to get reminders for your medications</p>
|
648 |
+
<button class="btn btn-warning" id="enableNotifications">
|
649 |
+
<span id="enableNotificationsText">Enable Notifications</span>
|
650 |
+
<span class="spinner" id="notificationSpinner" style="display: none;"></span>
|
651 |
+
</button>
|
652 |
+
</div>
|
653 |
+
|
654 |
<section class="dashboard">
|
655 |
<div class="stats">
|
656 |
+
<div class="stat-card animate-on-scroll">
|
657 |
+
<h3><i class="fas fa-clock"></i> Upcoming Today</h3>
|
658 |
<p id="upcomingCount">3</p>
|
659 |
</div>
|
660 |
+
<div class="stat-card animate-on-scroll" style="transition-delay: 0.1s;">
|
661 |
+
<h3><i class="fas fa-prescription-bottle-alt"></i> Medications</h3>
|
662 |
<p id="totalMeds">5</p>
|
663 |
</div>
|
664 |
+
<div class="stat-card animate-on-scroll" style="transition-delay: 0.2s;">
|
665 |
+
<h3><i class="fas fa-check-circle"></i> Adherence</h3>
|
666 |
<p id="adherenceRate">92%</p>
|
667 |
</div>
|
668 |
</div>
|
669 |
</section>
|
670 |
|
671 |
+
<section class="upcoming animate-on-scroll">
|
672 |
<div class="section-title">
|
673 |
<h2><i class="far fa-clock"></i> Upcoming Medications</h2>
|
674 |
+
<div class="dropdown">
|
675 |
+
<button class="btn btn-outline">
|
676 |
+
<i class="fas fa-ellipsis-v"></i>
|
677 |
+
</button>
|
678 |
+
</div>
|
679 |
</div>
|
680 |
|
681 |
<div class="medication-list" id="medicationList">
|
682 |
<!-- Medication items will be added here dynamically -->
|
683 |
</div>
|
684 |
+
|
685 |
+
<div class="empty-state" id="emptyState" style="display: none;">
|
686 |
+
<i class="fas fa-cloud"></i>
|
687 |
+
<h3>No medications scheduled</h3>
|
688 |
+
<p>Add your first medication to get started</p>
|
689 |
+
<button class="btn btn-primary" id="addFirstMed">
|
690 |
+
<i class="fas fa-plus"></i> Add Medication
|
691 |
+
</button>
|
692 |
+
</div>
|
693 |
</section>
|
694 |
</div>
|
695 |
|
|
|
697 |
<div class="modal" id="addMedModal">
|
698 |
<div class="modal-content">
|
699 |
<div class="modal-header">
|
700 |
+
<h2><i class="fas fa-plus-circle"></i> Add New Medication</h2>
|
701 |
<button class="close-btn" id="closeModal">×</button>
|
702 |
</div>
|
703 |
<form id="medicationForm">
|
704 |
<div class="form-group">
|
705 |
+
<label for="medName"><i class="fas fa-capsules"></i> Medication Name</label>
|
706 |
<input type="text" id="medName" class="form-control" placeholder="e.g. Ibuprofen" required>
|
707 |
</div>
|
708 |
<div class="form-group">
|
709 |
+
<label for="medDosage"><i class="fas fa-prescription-bottle-alt"></i> Dosage</label>
|
710 |
<input type="text" id="medDosage" class="form-control" placeholder="e.g. 200mg" required>
|
711 |
</div>
|
712 |
<div class="form-group">
|
713 |
+
<label for="medFrequency"><i class="fas fa-history"></i> Frequency</label>
|
714 |
<select id="medFrequency" class="form-control" required>
|
715 |
<option value="">Select frequency</option>
|
716 |
<option value="once">Once daily</option>
|
|
|
720 |
</select>
|
721 |
</div>
|
722 |
<div class="form-group">
|
723 |
+
<label><i class="far fa-clock"></i> Times</label>
|
724 |
<div class="time-inputs" id="timeInputs">
|
725 |
<div>
|
726 |
<input type="time" id="time1" class="form-control" required>
|
|
|
728 |
</div>
|
729 |
</div>
|
730 |
<div class="form-group">
|
731 |
+
<label for="medNotes"><i class="far fa-sticky-note"></i> Notes (Optional)</label>
|
732 |
<textarea id="medNotes" class="form-control" rows="3" placeholder="Special instructions..."></textarea>
|
733 |
</div>
|
734 |
<div class="modal-footer">
|
|
|
740 |
</div>
|
741 |
|
742 |
<!-- Notification -->
|
743 |
+
<div class="notification success" id="notification">
|
744 |
<i class="fas fa-check-circle"></i>
|
745 |
<span id="notificationText">Medication added successfully!</span>
|
746 |
</div>
|
747 |
|
748 |
<script>
|
749 |
+
// Enhanced sample data for medications with more details
|
750 |
const sampleMedications = [
|
751 |
{
|
752 |
id: 1,
|
|
|
754 |
dosage: "200mg",
|
755 |
time: "08:00",
|
756 |
isTaken: false,
|
757 |
+
note: "Take with breakfast",
|
758 |
+
category: "Pain Relief",
|
759 |
+
lastTaken: null
|
760 |
},
|
761 |
{
|
762 |
id: 2,
|
|
|
764 |
dosage: "1000IU",
|
765 |
time: "12:00",
|
766 |
isTaken: false,
|
767 |
+
note: "With lunch for better absorption",
|
768 |
+
category: "Supplement",
|
769 |
+
lastTaken: null
|
770 |
},
|
771 |
{
|
772 |
id: 3,
|
|
|
774 |
dosage: "1 tablet",
|
775 |
time: "18:00",
|
776 |
isTaken: false,
|
777 |
+
note: "",
|
778 |
+
category: "Supplement",
|
779 |
+
lastTaken: null
|
780 |
+
},
|
781 |
+
{
|
782 |
+
id: 4,
|
783 |
+
name: "Propranolol",
|
784 |
+
dosage: "40mg",
|
785 |
+
time: "10:00",
|
786 |
+
isTaken: true,
|
787 |
+
note: "For blood pressure",
|
788 |
+
category: "Cardiac",
|
789 |
+
lastTaken: "2023-07-20T08:00:00"
|
790 |
+
},
|
791 |
+
{
|
792 |
+
id: 5,
|
793 |
+
name: "Omeprazole",
|
794 |
+
dosage: "20mg",
|
795 |
+
time: "07:30",
|
796 |
+
isTaken: true,
|
797 |
+
note: "Before breakfast",
|
798 |
+
category: "Gastrointestinal",
|
799 |
+
lastTaken: "2023-07-20T07:30:00"
|
800 |
}
|
801 |
];
|
802 |
|
803 |
// DOM Elements
|
804 |
const medicationList = document.getElementById('medicationList');
|
805 |
const addMedBtn = document.getElementById('addMedBtn');
|
806 |
+
const addFirstMed = document.getElementById('addFirstMed');
|
807 |
const addMedModal = document.getElementById('addMedModal');
|
808 |
const closeModal = document.getElementById('closeModal');
|
809 |
const cancelBtn = document.getElementById('cancelBtn');
|
|
|
815 |
const upcomingCount = document.getElementById('upcomingCount');
|
816 |
const totalMeds = document.getElementById('totalMeds');
|
817 |
const adherenceRate = document.getElementById('adherenceRate');
|
818 |
+
const emptyState = document.getElementById('emptyState');
|
819 |
+
const permissionBanner = document.getElementById('permissionBanner');
|
820 |
+
const enableNotificationsBtn = document.getElementById('enableNotifications');
|
821 |
+
const enableNotificationsText = document.getElementById('enableNotificationsText');
|
822 |
+
const notificationSpinner = document.getElementById('notificationSpinner');
|
823 |
|
824 |
// Current medications
|
825 |
let medications = [...sampleMedications];
|
826 |
+
let nextId = 6;
|
827 |
+
let notificationPermissionGranted = false;
|
828 |
+
let notificationCheckInterval;
|
829 |
+
let scheduledNotifications = {};
|
830 |
|
831 |
// Initialize the app
|
832 |
function init() {
|
833 |
renderMedications();
|
834 |
updateStats();
|
835 |
+
checkNotificationPermission();
|
836 |
+
setupIntersectionObserver();
|
837 |
+
|
838 |
+
// Request permission when clicking the notification button
|
839 |
+
enableNotificationsBtn.addEventListener('click', requestNotificationPermission);
|
840 |
+
|
841 |
+
// Check every minute for due medications
|
842 |
+
notificationCheckInterval = setInterval(checkNotifications, 60000);
|
843 |
+
|
844 |
+
// Initial check
|
845 |
checkNotifications();
|
846 |
}
|
847 |
|
848 |
+
// Check and request notification permission
|
849 |
+
function checkNotificationPermission() {
|
850 |
+
if (!('Notification' in window)) {
|
851 |
+
console.log('This browser does not support desktop notification');
|
852 |
+
return;
|
853 |
+
}
|
854 |
+
|
855 |
+
if (Notification.permission === 'granted') {
|
856 |
+
notificationPermissionGranted = true;
|
857 |
+
permissionBanner.style.display = 'none';
|
858 |
+
} else if (Notification.permission !== 'denied') {
|
859 |
+
// Show the permission banner
|
860 |
+
permissionBanner.style.display = 'flex';
|
861 |
+
setTimeout(() => permissionBanner.classList.add('show'), 100);
|
862 |
+
}
|
863 |
+
}
|
864 |
+
|
865 |
+
// Request notification permission
|
866 |
+
function requestNotificationPermission() {
|
867 |
+
enableNotificationsText.textContent = 'Loading...';
|
868 |
+
notificationSpinner.style.display = 'inline-block';
|
869 |
+
|
870 |
+
Notification.requestPermission().then(permission => {
|
871 |
+
enableNotificationsText.textContent = 'Enable Notifications';
|
872 |
+
notificationSpinner.style.display = 'none';
|
873 |
+
|
874 |
+
if (permission === 'granted') {
|
875 |
+
notificationPermissionGranted = true;
|
876 |
+
showNotification('Notifications enabled! You will now receive reminders for your medications.', 'success');
|
877 |
+
permissionBanner.style.display = 'none';
|
878 |
+
|
879 |
+
// Schedule all existing medication notifications
|
880 |
+
medications.forEach(med => {
|
881 |
+
if (!med.isTaken) {
|
882 |
+
scheduleSystemNotification(med);
|
883 |
+
}
|
884 |
+
});
|
885 |
+
} else {
|
886 |
+
showNotification('Notifications are disabled. You can enable them later in your browser settings.', 'warning');
|
887 |
+
}
|
888 |
+
}).catch(err => {
|
889 |
+
enableNotificationsText.textContent = 'Enable Notifications';
|
890 |
+
notificationSpinner.style.display = 'none';
|
891 |
+
console.error('Error requesting notification permission:', err);
|
892 |
+
showNotification('Failed to enable notifications. Please try again later.', 'error');
|
893 |
+
});
|
894 |
+
}
|
895 |
+
|
896 |
+
// Show system notification
|
897 |
+
function showSystemNotification(title, options) {
|
898 |
+
if (!notificationPermissionGranted) return;
|
899 |
+
|
900 |
+
try {
|
901 |
+
// Close any existing notification with the same tag
|
902 |
+
if (options.tag) {
|
903 |
+
const existingNotifications = Notification.notifications || [];
|
904 |
+
existingNotifications.forEach(notification => {
|
905 |
+
if (notification.tag === options.tag) {
|
906 |
+
notification.close();
|
907 |
+
}
|
908 |
+
});
|
909 |
+
}
|
910 |
+
|
911 |
+
// Show new notification
|
912 |
+
const notification = new Notification(title, options);
|
913 |
+
|
914 |
+
// Add click handler if supported
|
915 |
+
if ('onshow' in notification) {
|
916 |
+
notification.onshow = function() {
|
917 |
+
console.log('Notification shown:', title);
|
918 |
+
};
|
919 |
+
}
|
920 |
+
|
921 |
+
if ('onclick' in notification) {
|
922 |
+
notification.onclick = function() {
|
923 |
+
console.log('Notification clicked:', title);
|
924 |
+
window.focus();
|
925 |
+
// You could focus on the specific medication item
|
926 |
+
};
|
927 |
+
}
|
928 |
+
|
929 |
+
if ('onclose' in notification) {
|
930 |
+
notification.onclose = function() {
|
931 |
+
console.log('Notification closed:', title);
|
932 |
+
};
|
933 |
+
}
|
934 |
+
|
935 |
+
return notification;
|
936 |
+
} catch (error) {
|
937 |
+
console.error('Error showing notification:', error);
|
938 |
+
return null;
|
939 |
+
}
|
940 |
+
}
|
941 |
+
|
942 |
+
// Schedule system notification for medication
|
943 |
+
function scheduleSystemNotification(medication) {
|
944 |
+
if (!notificationPermissionGranted) return;
|
945 |
+
|
946 |
+
// Clear any existing notification for this medication
|
947 |
+
if (scheduledNotifications[medication.id]) {
|
948 |
+
clearTimeout(scheduledNotifications[medication.id]);
|
949 |
+
delete scheduledNotifications[medication.id];
|
950 |
+
}
|
951 |
+
|
952 |
+
// Parse the medication time
|
953 |
+
const [hours, minutes] = medication.time.split(':').map(Number);
|
954 |
+
const now = new Date();
|
955 |
+
const notificationTime = new Date();
|
956 |
+
notificationTime.setHours(hours);
|
957 |
+
notificationTime.setMinutes(minutes);
|
958 |
+
notificationTime.setSeconds(0);
|
959 |
+
|
960 |
+
// If the time is in the past, schedule for tomorrow
|
961 |
+
if (notificationTime < now) {
|
962 |
+
notificationTime.setDate(notificationTime.getDate() + 1);
|
963 |
+
}
|
964 |
+
|
965 |
+
const timeUntilNotification = notificationTime.getTime() - now.getTime();
|
966 |
+
|
967 |
+
if (timeUntilNotification > 0 && timeUntilNotification < 24 * 60 * 60 * 1000) {
|
968 |
+
scheduledNotifications[medication.id] = setTimeout(() => {
|
969 |
+
const title = `Time to take ${medication.name}`;
|
970 |
+
const options = {
|
971 |
+
body: `Dosage: ${medication.dosage}\n${medication.note || ''}`,
|
972 |
+
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png',
|
973 |
+
tag: `medication-${medication.id}`,
|
974 |
+
requireInteraction: true,
|
975 |
+
vibrate: [200, 100, 200],
|
976 |
+
actions: [
|
977 |
+
{
|
978 |
+
action: 'mark-taken',
|
979 |
+
title: 'Mark as Taken',
|
980 |
+
icon: 'https://cdn-icons-png.flaticon.com/512/1828/1828640.png'
|
981 |
+
}
|
982 |
+
]
|
983 |
+
};
|
984 |
+
|
985 |
+
const notif = showSystemNotification(title, options);
|
986 |
+
|
987 |
+
if (notif) {
|
988 |
+
notif.onclick = (event) => {
|
989 |
+
// If a specific action was clicked
|
990 |
+
if (event.action === 'mark-taken') {
|
991 |
+
takeMedication(medication.id);
|
992 |
+
} else {
|
993 |
+
// Otherwise, focus the window
|
994 |
+
window.focus();
|
995 |
+
}
|
996 |
+
notif.close();
|
997 |
+
};
|
998 |
+
}
|
999 |
+
|
1000 |
+
// Auto-fade notification after 5 minutes
|
1001 |
+
setTimeout(() => {
|
1002 |
+
if (notif) notif.close();
|
1003 |
+
}, 5 * 60 * 1000);
|
1004 |
+
|
1005 |
+
}, timeUntilNotification);
|
1006 |
+
}
|
1007 |
+
}
|
1008 |
+
|
1009 |
// Render medications to the list
|
1010 |
function renderMedications() {
|
1011 |
medicationList.innerHTML = '';
|
|
|
1015 |
return a.time.localeCompare(b.time);
|
1016 |
});
|
1017 |
|
1018 |
+
const upcomingMeds = sortedMeds.filter(med => !med.isTaken);
|
1019 |
+
|
1020 |
+
if (upcomingMeds.length === 0) {
|
1021 |
+
emptyState.style.display = 'block';
|
1022 |
+
medicationList.style.display = 'none';
|
1023 |
+
} else {
|
1024 |
+
emptyState.style.display = 'none';
|
1025 |
+
medicationList.style.display = 'flex';
|
1026 |
+
|
1027 |
+
upcomingMeds.forEach((med, index) => {
|
1028 |
const medItem = document.createElement('div');
|
1029 |
+
medItem.className = 'medication-item animate-on-scroll';
|
1030 |
medItem.dataset.id = med.id;
|
1031 |
+
medItem.style.transitionDelay = `${index * 0.1}s`;
|
1032 |
|
1033 |
const [hours, minutes] = med.time.split(':');
|
|
|
1034 |
const hour = parseInt(hours);
|
1035 |
const amPm = hour >= 12 ? 'PM' : 'AM';
|
1036 |
const hour12 = hour % 12 || 12;
|
1037 |
+
const timeDisplay = `${hour12}:${minutes} ${amPm}`;
|
1038 |
+
|
1039 |
+
// Determine urgency color
|
1040 |
+
const now = new Date();
|
1041 |
+
const medTime = new Date();
|
1042 |
+
medTime.setHours(hour);
|
1043 |
+
medTime.setMinutes(minutes);
|
1044 |
+
|
1045 |
+
const timeDiff = (medTime - now) / (1000 * 60 * 60); // hours difference
|
1046 |
+
let urgencyClass = '';
|
1047 |
+
|
1048 |
+
if (timeDiff < 0) {
|
1049 |
+
// Past due
|
1050 |
+
urgencyClass = 'past-due';
|
1051 |
+
} else if (timeDiff < 1) {
|
1052 |
+
// Due soon (within 1 hour)
|
1053 |
+
urgencyClass = 'due-soon';
|
1054 |
+
}
|
1055 |
|
1056 |
medItem.innerHTML = `
|
1057 |
<div class="med-icon">
|
|
|
1059 |
</div>
|
1060 |
<div class="med-details">
|
1061 |
<h3>${med.name}</h3>
|
1062 |
+
<p>${med.dosage} • ${med.category || 'General'}</p>
|
1063 |
+
${med.note ? `<div class="med-note"><i class="fas fa-info-circle"></i> ${med.note}</div>` : ''}
|
1064 |
</div>
|
1065 |
+
<div class="med-time ${urgencyClass}">
|
1066 |
+
<p>${timeDisplay} ${urgencyClass === 'past-due' ? ' (Past Due)' : urgencyClass === 'due-soon' ? ' (Soon!)' : ''}</p>
|
1067 |
+
<button class="btn btn-success take-btn">
|
1068 |
+
<i class="fas fa-check"></i> Mark Taken
|
1069 |
</button>
|
1070 |
</div>
|
1071 |
`;
|
1072 |
|
1073 |
+
if (urgencyClass === 'due-soon' || urgencyClass === 'past-due') {
|
1074 |
+
medItem.style.animation = urgencyClass === 'past-due' ? 'pulse 2s infinite' : 'pulse 3s infinite';
|
1075 |
+
}
|
1076 |
+
|
1077 |
medicationList.appendChild(medItem);
|
1078 |
|
1079 |
// Add event listener to the take button
|
1080 |
medItem.querySelector('.take-btn').addEventListener('click', () => takeMedication(med.id));
|
1081 |
+
});
|
1082 |
+
|
1083 |
+
// Trigger animation when elements are in view
|
1084 |
+
setTimeout(() => {
|
1085 |
+
const observer = new IntersectionObserver((entries) => {
|
1086 |
+
entries.forEach(entry => {
|
1087 |
+
if (entry.isIntersecting) {
|
1088 |
+
entry.target.classList.add('show');
|
1089 |
+
}
|
1090 |
+
});
|
1091 |
+
}, { threshold: 0.1 });
|
1092 |
+
|
1093 |
+
document.querySelectorAll('.animate-on-scroll').forEach(el => {
|
1094 |
+
observer.observe(el);
|
1095 |
+
});
|
1096 |
+
}, 100);
|
1097 |
+
}
|
1098 |
}
|
1099 |
|
1100 |
// Mark medication as taken
|
|
|
1102 |
const index = medications.findIndex(med => med.id === id);
|
1103 |
if (index !== -1) {
|
1104 |
medications[index].isTaken = true;
|
1105 |
+
medications[index].lastTaken = new Date().toISOString();
|
1106 |
renderMedications();
|
1107 |
updateStats();
|
1108 |
+
showNotification(`Marked ${medications[index].name} as taken.`, 'success');
|
1109 |
+
|
1110 |
+
// Cancel any scheduled notification for this medication
|
1111 |
+
if (scheduledNotifications[id]) {
|
1112 |
+
clearTimeout(scheduledNotifications[id]);
|
1113 |
+
delete scheduledNotifications[id];
|
1114 |
+
}
|
1115 |
|
1116 |
+
// Schedule notification for next dose if recurring
|
1117 |
+
if (notificationPermissionGranted) {
|
1118 |
+
scheduleSystemNotification(medications[index]);
|
1119 |
+
}
|
1120 |
}
|
1121 |
}
|
1122 |
|
1123 |
+
// Show notification with different types
|
1124 |
+
function showNotification(message, type = 'success') {
|
1125 |
+
// Show in-page notification
|
1126 |
+
notification.className = `notification ${type}`;
|
1127 |
notificationText.textContent = message;
|
1128 |
notification.classList.add('show');
|
1129 |
|
1130 |
setTimeout(() => {
|
1131 |
notification.classList.remove('show');
|
1132 |
}, 3000);
|
1133 |
+
|
1134 |
+
// If permission granted and it's a warning/important notification, show system notification
|
1135 |
+
if (notificationPermissionGranted && (type === 'warning' || type === 'error')) {
|
1136 |
+
try {
|
1137 |
+
const notification = new Notification('MedTrack Alert', {
|
1138 |
+
body: message,
|
1139 |
+
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png',
|
1140 |
+
});
|
1141 |
+
|
1142 |
+
setTimeout(() => notification.close(), 5000);
|
1143 |
+
} catch (e) {
|
1144 |
+
console.warn('Could not show system notification', e);
|
1145 |
+
}
|
1146 |
+
}
|
1147 |
}
|
1148 |
|
1149 |
// Check if any medications are due
|
|
|
1155 |
|
1156 |
medications.forEach(med => {
|
1157 |
if (!med.isTaken && med.time === currentTime) {
|
1158 |
+
const message = `Time to take ${med.name} (${med.dosage})${med.note ? ': ' + med.note : ''}`;
|
1159 |
+
showNotification(message, 'warning');
|
1160 |
+
|
1161 |
+
// If notifications are enabled, also show system notification
|
1162 |
+
if (notificationPermissionGranted) {
|
1163 |
+
try {
|
1164 |
+
const notification = new Notification(`Time to take ${med.name}`, {
|
1165 |
+
body: message,
|
1166 |
+
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png',
|
1167 |
+
requireInteraction: true,
|
1168 |
+
vibrate: [200, 100, 200]
|
1169 |
+
});
|
1170 |
+
|
1171 |
+
// Auto-close after 5 minutes
|
1172 |
+
setTimeout(() => notification.close(), 5 * 60 * 1000);
|
1173 |
+
} catch (e) {
|
1174 |
+
console.warn('Could not show system notification', e);
|
1175 |
+
}
|
1176 |
+
}
|
1177 |
}
|
1178 |
});
|
1179 |
}
|
|
|
1181 |
// Schedule next notification for recurring medications
|
1182 |
function scheduleNextNotification(medication) {
|
1183 |
// In a real app, this would schedule an actual notification
|
1184 |
+
if (notificationPermissionGranted) {
|
1185 |
+
scheduleSystemNotification(medication);
|
1186 |
+
}
|
1187 |
}
|
1188 |
|
1189 |
+
// Update statistics with animation
|
1190 |
function updateStats() {
|
1191 |
const total = medications.length;
|
1192 |
const upcoming = medications.filter(med => !med.isTaken).length;
|
1193 |
const taken = medications.filter(med => med.isTaken).length;
|
1194 |
const adherence = total > 0 ? Math.round((taken / total) * 100) : 0;
|
1195 |
|
1196 |
+
// Animate count changes
|
1197 |
+
animateValue('upcomingCount', parseInt(upcomingCount.textContent), upcoming, 500);
|
1198 |
+
animateValue('totalMeds', parseInt(totalMeds.textContent), total, 500);
|
1199 |
+
animateValue('adherenceRate', parseInt(adherenceRate.textContent.replace('%', '')), adherence, 500);
|
1200 |
+
}
|
1201 |
+
|
1202 |
+
// Animate numeric value changes
|
1203 |
+
function animateValue(id, start, end, duration) {
|
1204 |
+
const obj = document.getElementById(id);
|
1205 |
+
let startTimestamp = null;
|
1206 |
+
const step = (timestamp) => {
|
1207 |
+
if (!startTimestamp) startTimestamp = timestamp;
|
1208 |
+
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
|
1209 |
+
const val = Math.floor(progress * (end - start) + start);
|
1210 |
+
obj.textContent = id === 'adherenceRate' ? `${val}%` : val;
|
1211 |
+
if (progress < 1) {
|
1212 |
+
window.requestAnimationFrame(step);
|
1213 |
+
}
|
1214 |
+
};
|
1215 |
+
window.requestAnimationFrame(step);
|
1216 |
}
|
1217 |
|
1218 |
+
// Show modal with animation
|
1219 |
function showModal() {
|
1220 |
addMedModal.style.display = 'flex';
|
1221 |
document.body.style.overflow = 'hidden';
|
1222 |
+
setTimeout(() => addMedModal.classList.add('show'), 10);
|
1223 |
}
|
1224 |
|
1225 |
+
// Hide modal with animation
|
1226 |
function hideModal() {
|
1227 |
+
addMedModal.classList.remove('show');
|
1228 |
+
setTimeout(() => {
|
1229 |
+
addMedModal.style.display = 'none';
|
1230 |
+
document.body.style.overflow = 'auto';
|
1231 |
+
medicationForm.reset();
|
1232 |
+
while (timeInputs.children.length > 1) {
|
1233 |
+
timeInputs.removeChild(timeInputs.lastChild);
|
1234 |
+
}
|
1235 |
+
}, 300);
|
1236 |
}
|
1237 |
|
1238 |
+
// Add new medication with additional fields
|
1239 |
function addMedication(name, dosage, frequency, times, note) {
|
1240 |
+
const categories = ["Pain Relief", "Supplement", "Antibiotic", "Cardiac", "Gastrointestinal", "Other"];
|
1241 |
+
const randomCategory = categories[Math.floor(Math.random() * categories.length)];
|
1242 |
+
|
1243 |
times.forEach(time => {
|
1244 |
const newMed = {
|
1245 |
id: nextId++,
|
|
|
1247 |
dosage,
|
1248 |
time,
|
1249 |
isTaken: false,
|
1250 |
+
note,
|
1251 |
+
category: randomCategory,
|
1252 |
+
lastTaken: null
|
1253 |
};
|
1254 |
|
1255 |
medications.push(newMed);
|
1256 |
+
|
1257 |
+
// Schedule notification for this medication if permission granted
|
1258 |
+
if (notificationPermissionGranted) {
|
1259 |
+
scheduleSystemNotification(newMed);
|
1260 |
+
}
|
1261 |
});
|
1262 |
|
1263 |
renderMedications();
|
1264 |
updateStats();
|
1265 |
+
showNotification(`Added ${name} to your medications.`, 'success');
|
1266 |
hideModal();
|
1267 |
}
|
1268 |
|
1269 |
+
// Setup intersection observer for scroll animations
|
1270 |
+
function setupIntersectionObserver() {
|
1271 |
+
const observer = new IntersectionObserver((entries) => {
|
1272 |
+
entries.forEach(entry => {
|
1273 |
+
if (entry.isIntersecting) {
|
1274 |
+
entry.target.classList.add('show');
|
1275 |
+
}
|
1276 |
+
});
|
1277 |
+
}, { threshold: 0.1 });
|
1278 |
+
|
1279 |
+
document.querySelectorAll('.animate-on-scroll').forEach(el => {
|
1280 |
+
observer.observe(el);
|
1281 |
+
});
|
1282 |
+
}
|
1283 |
+
|
1284 |
// Event Listeners
|
1285 |
addMedBtn.addEventListener('click', showModal);
|
1286 |
+
addFirstMed.addEventListener('click', showModal);
|
1287 |
closeModal.addEventListener('click', hideModal);
|
1288 |
cancelBtn.addEventListener('click', hideModal);
|
1289 |
|
|
|
1323 |
});
|
1324 |
|
1325 |
if (times.length === 0) {
|
1326 |
+
showNotification('Please enter at least one time', 'error');
|
1327 |
return;
|
1328 |
}
|
1329 |
|
1330 |
addMedication(name, dosage, frequency, times, note);
|
1331 |
});
|
1332 |
|
1333 |
+
// Close modal when clicking outside
|
1334 |
+
addMedModal.addEventListener('click', function(e) {
|
1335 |
+
if (e.target === addMedModal) {
|
1336 |
+
hideModal();
|
1337 |
+
}
|
1338 |
+
});
|
1339 |
+
|
1340 |
+
// Empty state button
|
1341 |
+
addFirstMed.addEventListener('click', showModal);
|
1342 |
+
|
1343 |
// Initialize the app
|
1344 |
init();
|
1345 |
|
1346 |
+
// Add pulse animation for emergencies
|
1347 |
+
const style = document.createElement('style');
|
1348 |
+
style.textContent = `
|
1349 |
+
@keyframes pulse {
|
1350 |
+
0% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.4); }
|
1351 |
+
70% { box-shadow: 0 0 0 10px rgba(231, 76, 60, 0); }
|
1352 |
+
100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0); }
|
1353 |
+
}
|
1354 |
+
|
1355 |
+
.due-soon p, .past-due p {
|
1356 |
+
color: #e74c3c !important;
|
1357 |
+
}
|
1358 |
+
`;
|
1359 |
+
document.head.appendChild(style);
|
1360 |
</script>
|
1361 |
+
</body>
|
1362 |
</html>
|