|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>MedTrack - Premium Medication Reminder</title> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
|
<style> |
|
:root { |
|
--primary: #6c5ce7; |
|
--primary-light: #a29bfe; |
|
--primary-dark: #5649c2; |
|
--secondary: #00cec9; |
|
--success: #00b894; |
|
--danger: #d63031; |
|
--warning: #fdcb6e; |
|
--light: #f5f6fa; |
|
--dark: #2d3436; |
|
--gray: #636e72; |
|
--gray-light: #dfe6e9; |
|
--white: #ffffff; |
|
--glass: rgba(255, 255, 255, 0.25); |
|
--glass-border: rgba(255, 255, 255, 0.4); |
|
} |
|
|
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
font-family: 'Poppins', sans-serif; |
|
} |
|
|
|
body { |
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
|
color: var(--dark); |
|
line-height: 1.6; |
|
min-height: 100vh; |
|
} |
|
|
|
|
|
.bg-shapes { |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
overflow: hidden; |
|
z-index: -1; |
|
} |
|
|
|
.bg-shape { |
|
position: absolute; |
|
border-radius: 50%; |
|
opacity: 0.1; |
|
filter: blur(50px); |
|
} |
|
|
|
.shape-1 { |
|
width: 400px; |
|
height: 400px; |
|
background: var(--primary); |
|
top: -100px; |
|
left: -100px; |
|
} |
|
|
|
.shape-2 { |
|
width: 600px; |
|
height: 600px; |
|
background: var(--secondary); |
|
bottom: -200px; |
|
right: -200px; |
|
} |
|
|
|
.container { |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
animation: fadeIn 0.8s ease-out; |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(20px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 40px; |
|
padding-bottom: 20px; |
|
border-bottom: 1px solid rgba(255, 255, 255, 0.5); |
|
} |
|
|
|
.logo { |
|
display: flex; |
|
align-items: center; |
|
gap: 15px; |
|
} |
|
|
|
.logo-icon { |
|
width: 50px; |
|
height: 50px; |
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); |
|
border-radius: 12px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.3); |
|
} |
|
|
|
.logo-icon i { |
|
color: var(--white); |
|
font-size: 24px; |
|
} |
|
|
|
.logo-text h1 { |
|
font-size: 28px; |
|
font-weight: 700; |
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
margin-bottom: 3px; |
|
} |
|
|
|
.logo-text p { |
|
font-size: 12px; |
|
color: var(--gray); |
|
letter-spacing: 1px; |
|
text-transform: uppercase; |
|
} |
|
|
|
|
|
.btn { |
|
padding: 12px 20px; |
|
border: none; |
|
border-radius: 12px; |
|
cursor: pointer; |
|
font-weight: 600; |
|
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); |
|
display: inline-flex; |
|
align-items: center; |
|
gap: 10px; |
|
font-size: 14px; |
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.btn i { |
|
font-size: 16px; |
|
} |
|
|
|
.btn-primary { |
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); |
|
color: var(--white); |
|
} |
|
|
|
.btn-primary:hover { |
|
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.4); |
|
transform: translateY(-2px); |
|
} |
|
|
|
.btn-secondary { |
|
background: linear-gradient(135deg, var(--secondary) 0%, #00a6b3 100%); |
|
color: var(--white); |
|
} |
|
|
|
.btn-secondary:hover { |
|
box-shadow: 0 8px 20px rgba(0, 206, 201, 0.4); |
|
transform: translateY(-2px); |
|
} |
|
|
|
.btn-success { |
|
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%); |
|
color: var(--white); |
|
} |
|
|
|
.btn-success:hover { |
|
box-shadow: 0 8px 20px rgba(0, 184, 148, 0.4); |
|
transform: translateY(-2px); |
|
} |
|
|
|
.btn-danger { |
|
background: linear-gradient(135deg, var(--danger) 0%, #c02929 100%); |
|
color: var(--white); |
|
} |
|
|
|
.btn-danger:hover { |
|
box-shadow: 0 8px 20px rgba(214, 48, 49, 0.4); |
|
transform: translateY(-2px); |
|
} |
|
|
|
.btn-outline { |
|
background-color: transparent; |
|
border: 2px solid var(--glass-border); |
|
color: var(--dark); |
|
backdrop-filter: blur(5px); |
|
} |
|
|
|
.btn-outline:hover { |
|
background-color: var(--glass); |
|
transform: translateY(-2px); |
|
} |
|
|
|
|
|
.dashboard { |
|
margin-bottom: 40px; |
|
} |
|
|
|
.stats { |
|
display: grid; |
|
grid-template-columns: repeat(3, 1fr); |
|
gap: 20px; |
|
margin-bottom: 30px; |
|
} |
|
|
|
.stat-card { |
|
background: var(--glass); |
|
backdrop-filter: blur(10px); |
|
border: 1px solid var(--glass-border); |
|
padding: 25px; |
|
border-radius: 16px; |
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.stat-card:hover { |
|
transform: translateY(-5px); |
|
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.2); |
|
} |
|
|
|
.stat-card h3 { |
|
font-size: 14px; |
|
font-weight: 500; |
|
color: var(--gray); |
|
margin-bottom: 10px; |
|
} |
|
|
|
.stat-card p { |
|
font-size: 28px; |
|
font-weight: 700; |
|
} |
|
|
|
|
|
.upcoming { |
|
background: var(--glass); |
|
backdrop-filter: blur(10px); |
|
border: 1px solid var(--glass-border); |
|
border-radius: 16px; |
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1); |
|
padding: 25px; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.upcoming:hover { |
|
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.2); |
|
} |
|
|
|
.section-title { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 25px; |
|
} |
|
|
|
.section-title h2 { |
|
font-size: 20px; |
|
font-weight: 600; |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
} |
|
|
|
.section-title i { |
|
color: var(--primary); |
|
} |
|
|
|
.medication-list { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 15px; |
|
} |
|
|
|
.medication-item { |
|
display: flex; |
|
align-items: center; |
|
padding: 20px; |
|
border-radius: 12px; |
|
background: var(--white); |
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05); |
|
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.medication-item:hover { |
|
transform: translateY(-3px); |
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.medication-item::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 5px; |
|
height: 100%; |
|
background: linear-gradient(to bottom, var(--primary), var(--secondary)); |
|
} |
|
|
|
.med-icon { |
|
width: 55px; |
|
height: 55px; |
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
|
border-radius: 12px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
margin-right: 20px; |
|
color: var(--white); |
|
font-size: 22px; |
|
box-shadow: 0 4px 15px rgba(108, 92, 231, 0.3); |
|
} |
|
|
|
.med-details { |
|
flex: 1; |
|
} |
|
|
|
.med-details h3 { |
|
font-size: 18px; |
|
margin-bottom: 8px; |
|
font-weight: 600; |
|
} |
|
|
|
.med-details p { |
|
font-size: 14px; |
|
color: var(--gray); |
|
} |
|
|
|
.med-note { |
|
font-size: 13px; |
|
color: var(--gray); |
|
margin-top: 5px; |
|
display: flex; |
|
align-items: center; |
|
gap: 5px; |
|
} |
|
|
|
.med-time { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: flex-end; |
|
} |
|
|
|
.med-time p { |
|
font-weight: 600; |
|
margin-bottom: 10px; |
|
font-size: 15px; |
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
} |
|
|
|
|
|
.modal { |
|
display: none; |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(45, 52, 54, 0.8); |
|
z-index: 1000; |
|
justify-content: center; |
|
align-items: center; |
|
backdrop-filter: blur(5px); |
|
animation: fadeIn 0.3s ease-out; |
|
} |
|
|
|
.modal-content { |
|
background: var(--white); |
|
padding: 30px; |
|
border-radius: 20px; |
|
width: 90%; |
|
max-width: 500px; |
|
max-height: 90vh; |
|
overflow-y: auto; |
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3); |
|
transform: translateY(0); |
|
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); |
|
} |
|
|
|
.modal.show .modal-content { |
|
animation: modalSlideIn 0.4s ease-out; |
|
} |
|
|
|
@keyframes modalSlideIn { |
|
from { transform: translateY(50px); opacity: 0; } |
|
to { transform: translateY(0); opacity: 1; } |
|
} |
|
|
|
.modal-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 25px; |
|
} |
|
|
|
.modal-header h2 { |
|
font-size: 24px; |
|
font-weight: 600; |
|
color: var(--primary); |
|
} |
|
|
|
.close-btn { |
|
background: none; |
|
border: none; |
|
font-size: 28px; |
|
cursor: pointer; |
|
color: var(--gray); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.close-btn:hover { |
|
color: var(--danger); |
|
transform: rotate(90deg); |
|
} |
|
|
|
.form-group { |
|
margin-bottom: 25px; |
|
} |
|
|
|
.form-group label { |
|
display: block; |
|
margin-bottom: 10px; |
|
font-weight: 500; |
|
color: var(--dark); |
|
} |
|
|
|
.form-control { |
|
width: 100%; |
|
padding: 14px 20px; |
|
border: 2px solid var(--gray-light); |
|
border-radius: 12px; |
|
font-size: 16px; |
|
transition: all 0.3s ease; |
|
background-color: var(--light); |
|
} |
|
|
|
.form-control:focus { |
|
outline: none; |
|
border-color: var(--primary); |
|
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2); |
|
} |
|
|
|
.time-inputs { |
|
display: grid; |
|
grid-template-columns: 1fr 1fr; |
|
gap: 15px; |
|
} |
|
|
|
textarea.form-control { |
|
min-height: 100px; |
|
resize: vertical; |
|
} |
|
|
|
.modal-footer { |
|
display: flex; |
|
justify-content: flex-end; |
|
gap: 15px; |
|
margin-top: 30px; |
|
} |
|
|
|
|
|
.notification { |
|
position: fixed; |
|
bottom: 30px; |
|
right: 30px; |
|
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%); |
|
color: var(--white); |
|
padding: 18px 30px; |
|
border-radius: 12px; |
|
box-shadow: 0 10px 30px rgba(0, 184, 148, 0.3); |
|
display: flex; |
|
align-items: center; |
|
gap: 15px; |
|
transform: translateX(150%); |
|
opacity: 0; |
|
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); |
|
z-index: 100; |
|
} |
|
|
|
.notification.show { |
|
transform: translateX(0); |
|
opacity: 1; |
|
} |
|
|
|
.notification i { |
|
font-size: 24px; |
|
} |
|
|
|
.notification.success { |
|
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%); |
|
} |
|
|
|
.notification.warning { |
|
background: linear-gradient(135deg, var(--warning) 0%, #f8a84a 100%); |
|
} |
|
|
|
.notification.error { |
|
background: linear-gradient(135deg, var(--danger) 0%, #c02929 100%); |
|
} |
|
|
|
|
|
.empty-state { |
|
text-align: center; |
|
padding: 40px 0; |
|
} |
|
|
|
.empty-state i { |
|
font-size: 60px; |
|
color: var(--gray-light); |
|
margin-bottom: 20px; |
|
} |
|
|
|
.empty-state h3 { |
|
font-size: 18px; |
|
color: var(--gray); |
|
margin-bottom: 10px; |
|
} |
|
|
|
.empty-state p { |
|
font-size: 14px; |
|
color: var(--gray-light); |
|
margin-bottom: 20px; |
|
} |
|
|
|
|
|
.notification-permission { |
|
background: var(--glass); |
|
backdrop-filter: blur(10px); |
|
border: 1px solid var(--glass-border); |
|
border-radius: 16px; |
|
padding: 20px; |
|
margin-bottom: 30px; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
animation: fadeIn 0.6s ease-out; |
|
} |
|
|
|
.notification-permission p { |
|
font-size: 14px; |
|
color: var(--gray); |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
} |
|
|
|
.notification-permission i { |
|
color: var(--warning); |
|
font-size: 18px; |
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
.container { |
|
padding: 20px; |
|
} |
|
|
|
.stats { |
|
grid-template-columns: 1fr; |
|
} |
|
|
|
.medication-item { |
|
flex-direction: column; |
|
text-align: center; |
|
} |
|
|
|
.med-icon { |
|
margin-right: 0; |
|
margin-bottom: 20px; |
|
} |
|
|
|
.med-time { |
|
align-items: center; |
|
margin-top: 15px; |
|
} |
|
|
|
.time-inputs { |
|
grid-template-columns: 1fr; |
|
} |
|
} |
|
|
|
|
|
.animate-on-scroll { |
|
opacity: 0; |
|
transform: translateY(30px); |
|
transition: all 0.6s ease-out; |
|
} |
|
|
|
.animate-on-scroll.show { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
|
|
|
|
.spinner { |
|
display: inline-block; |
|
width: 20px; |
|
height: 20px; |
|
border: 3px solid rgba(255,255,255,.3); |
|
border-radius: 50%; |
|
border-top-color: var(--white); |
|
animation: spin 1s ease-in-out infinite; |
|
} |
|
|
|
@keyframes spin { |
|
to { transform: rotate(360deg); } |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="bg-shapes"> |
|
<div class="bg-shape shape-1"></div> |
|
<div class="bg-shape shape-2"></div> |
|
</div> |
|
|
|
<div class="container"> |
|
<header> |
|
<div class="logo"> |
|
<div class="logo-icon"> |
|
<i class="fas fa-pills"></i> |
|
</div> |
|
<div class="logo-text"> |
|
<h1>MedTrack</h1> |
|
<p>Premium Medication Manager</p> |
|
</div> |
|
</div> |
|
<button class="btn btn-primary" id="addMedBtn"> |
|
<i class="fas fa-plus"></i> Add Medication |
|
</button> |
|
</header> |
|
|
|
|
|
<div class="notification-permission animate-on-scroll" id="permissionBanner" style="display: none;"> |
|
<p><i class="fas fa-bell"></i> Enable notifications to get reminders for your medications</p> |
|
<button class="btn btn-warning" id="enableNotifications"> |
|
<span id="enableNotificationsText">Enable Notifications</span> |
|
<span class="spinner" id="notificationSpinner" style="display: none;"></span> |
|
</button> |
|
</div> |
|
|
|
<section class="dashboard"> |
|
<div class="stats"> |
|
<div class="stat-card animate-on-scroll"> |
|
<h3><i class="fas fa-clock"></i> Upcoming Today</h3> |
|
<p id="upcomingCount">3</p> |
|
</div> |
|
<div class="stat-card animate-on-scroll" style="transition-delay: 0.1s;"> |
|
<h3><i class="fas fa-prescription-bottle-alt"></i> Medications</h3> |
|
<p id="totalMeds">5</p> |
|
</div> |
|
<div class="stat-card animate-on-scroll" style="transition-delay: 0.2s;"> |
|
<h3><i class="fas fa-check-circle"></i> Adherence</h3> |
|
<p id="adherenceRate">92%</p> |
|
</div> |
|
</div> |
|
</section> |
|
|
|
<section class="upcoming animate-on-scroll"> |
|
<div class="section-title"> |
|
<h2><i class="far fa-clock"></i> Upcoming Medications</h2> |
|
<div class="dropdown"> |
|
<button class="btn btn-outline"> |
|
<i class="fas fa-ellipsis-v"></i> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="medication-list" id="medicationList"> |
|
|
|
</div> |
|
|
|
<div class="empty-state" id="emptyState" style="display: none;"> |
|
<i class="fas fa-cloud"></i> |
|
<h3>No medications scheduled</h3> |
|
<p>Add your first medication to get started</p> |
|
<button class="btn btn-primary" id="addFirstMed"> |
|
<i class="fas fa-plus"></i> Add Medication |
|
</button> |
|
</div> |
|
</section> |
|
</div> |
|
|
|
|
|
<div class="modal" id="addMedModal"> |
|
<div class="modal-content"> |
|
<div class="modal-header"> |
|
<h2><i class="fas fa-plus-circle"></i> Add New Medication</h2> |
|
<button class="close-btn" id="closeModal">×</button> |
|
</div> |
|
<form id="medicationForm"> |
|
<div class="form-group"> |
|
<label for="medName"><i class="fas fa-capsules"></i> Medication Name</label> |
|
<input type="text" id="medName" class="form-control" placeholder="e.g. Ibuprofen" required> |
|
</div> |
|
<div class="form-group"> |
|
<label for="medDosage"><i class="fas fa-prescription-bottle-alt"></i> Dosage</label> |
|
<input type="text" id="medDosage" class="form-control" placeholder="e.g. 200mg" required> |
|
</div> |
|
<div class="form-group"> |
|
<label for="medFrequency"><i class="fas fa-history"></i> Frequency</label> |
|
<select id="medFrequency" class="form-control" required> |
|
<option value="">Select frequency</option> |
|
<option value="once">Once daily</option> |
|
<option value="twice">Twice daily</option> |
|
<option value="thrice">Three times daily</option> |
|
<option value="as_needed">As needed</option> |
|
</select> |
|
</div> |
|
<div class="form-group"> |
|
<label><i class="far fa-clock"></i> Times</label> |
|
<div class="time-inputs" id="timeInputs"> |
|
<div> |
|
<input type="time" id="time1" class="form-control" required> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="form-group"> |
|
<label for="medNotes"><i class="far fa-sticky-note"></i> Notes (Optional)</label> |
|
<textarea id="medNotes" class="form-control" rows="3" placeholder="Special instructions..."></textarea> |
|
</div> |
|
<div class="modal-footer"> |
|
<button type="button" class="btn btn-outline" id="cancelBtn">Cancel</button> |
|
<button type="submit" class="btn btn-primary">Add Medication</button> |
|
</div> |
|
</form> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="notification success" id="notification"> |
|
<i class="fas fa-check-circle"></i> |
|
<span id="notificationText">Medication added successfully!</span> |
|
</div> |
|
|
|
<script> |
|
|
|
const sampleMedications = [ |
|
{ |
|
id: 1, |
|
name: "Ibuprofen", |
|
dosage: "200mg", |
|
time: "08:00", |
|
isTaken: false, |
|
note: "Take with breakfast", |
|
category: "Pain Relief", |
|
lastTaken: null |
|
}, |
|
{ |
|
id: 2, |
|
name: "Vitamin D", |
|
dosage: "1000IU", |
|
time: "12:00", |
|
isTaken: false, |
|
note: "With lunch for better absorption", |
|
category: "Supplement", |
|
lastTaken: null |
|
}, |
|
{ |
|
id: 3, |
|
name: "Multivitamin", |
|
dosage: "1 tablet", |
|
time: "18:00", |
|
isTaken: false, |
|
note: "", |
|
category: "Supplement", |
|
lastTaken: null |
|
}, |
|
{ |
|
id: 4, |
|
name: "Propranolol", |
|
dosage: "40mg", |
|
time: "10:00", |
|
isTaken: true, |
|
note: "For blood pressure", |
|
category: "Cardiac", |
|
lastTaken: "2023-07-20T08:00:00" |
|
}, |
|
{ |
|
id: 5, |
|
name: "Omeprazole", |
|
dosage: "20mg", |
|
time: "07:30", |
|
isTaken: true, |
|
note: "Before breakfast", |
|
category: "Gastrointestinal", |
|
lastTaken: "2023-07-20T07:30:00" |
|
} |
|
]; |
|
|
|
|
|
const medicationList = document.getElementById('medicationList'); |
|
const addMedBtn = document.getElementById('addMedBtn'); |
|
const addFirstMed = document.getElementById('addFirstMed'); |
|
const addMedModal = document.getElementById('addMedModal'); |
|
const closeModal = document.getElementById('closeModal'); |
|
const cancelBtn = document.getElementById('cancelBtn'); |
|
const medicationForm = document.getElementById('medicationForm'); |
|
const medFrequency = document.getElementById('medFrequency'); |
|
const timeInputs = document.getElementById('timeInputs'); |
|
const notification = document.getElementById('notification'); |
|
const notificationText = document.getElementById('notificationText'); |
|
const upcomingCount = document.getElementById('upcomingCount'); |
|
const totalMeds = document.getElementById('totalMeds'); |
|
const adherenceRate = document.getElementById('adherenceRate'); |
|
const emptyState = document.getElementById('emptyState'); |
|
const permissionBanner = document.getElementById('permissionBanner'); |
|
const enableNotificationsBtn = document.getElementById('enableNotifications'); |
|
const enableNotificationsText = document.getElementById('enableNotificationsText'); |
|
const notificationSpinner = document.getElementById('notificationSpinner'); |
|
|
|
|
|
let medications = [...sampleMedications]; |
|
let nextId = 6; |
|
let notificationPermissionGranted = false; |
|
let notificationCheckInterval; |
|
let scheduledNotifications = {}; |
|
|
|
|
|
function init() { |
|
renderMedications(); |
|
updateStats(); |
|
checkNotificationPermission(); |
|
setupIntersectionObserver(); |
|
|
|
|
|
enableNotificationsBtn.addEventListener('click', requestNotificationPermission); |
|
|
|
|
|
notificationCheckInterval = setInterval(checkNotifications, 60000); |
|
|
|
|
|
checkNotifications(); |
|
} |
|
|
|
|
|
function checkNotificationPermission() { |
|
if (!('Notification' in window)) { |
|
console.log('This browser does not support desktop notification'); |
|
return; |
|
} |
|
|
|
if (Notification.permission === 'granted') { |
|
notificationPermissionGranted = true; |
|
permissionBanner.style.display = 'none'; |
|
} else if (Notification.permission !== 'denied') { |
|
|
|
permissionBanner.style.display = 'flex'; |
|
setTimeout(() => permissionBanner.classList.add('show'), 100); |
|
} |
|
} |
|
|
|
|
|
function requestNotificationPermission() { |
|
enableNotificationsText.textContent = 'Loading...'; |
|
notificationSpinner.style.display = 'inline-block'; |
|
|
|
Notification.requestPermission().then(permission => { |
|
enableNotificationsText.textContent = 'Enable Notifications'; |
|
notificationSpinner.style.display = 'none'; |
|
|
|
if (permission === 'granted') { |
|
notificationPermissionGranted = true; |
|
showNotification('Notifications enabled! You will now receive reminders for your medications.', 'success'); |
|
permissionBanner.style.display = 'none'; |
|
|
|
|
|
medications.forEach(med => { |
|
if (!med.isTaken) { |
|
scheduleSystemNotification(med); |
|
} |
|
}); |
|
} else { |
|
showNotification('Notifications are disabled. You can enable them later in your browser settings.', 'warning'); |
|
} |
|
}).catch(err => { |
|
enableNotificationsText.textContent = 'Enable Notifications'; |
|
notificationSpinner.style.display = 'none'; |
|
console.error('Error requesting notification permission:', err); |
|
showNotification('Failed to enable notifications. Please try again later.', 'error'); |
|
}); |
|
} |
|
|
|
|
|
function showSystemNotification(title, options) { |
|
if (!notificationPermissionGranted) return; |
|
|
|
try { |
|
|
|
if (options.tag) { |
|
const existingNotifications = Notification.notifications || []; |
|
existingNotifications.forEach(notification => { |
|
if (notification.tag === options.tag) { |
|
notification.close(); |
|
} |
|
}); |
|
} |
|
|
|
|
|
const notification = new Notification(title, options); |
|
|
|
|
|
if ('onshow' in notification) { |
|
notification.onshow = function() { |
|
console.log('Notification shown:', title); |
|
}; |
|
} |
|
|
|
if ('onclick' in notification) { |
|
notification.onclick = function() { |
|
console.log('Notification clicked:', title); |
|
window.focus(); |
|
|
|
}; |
|
} |
|
|
|
if ('onclose' in notification) { |
|
notification.onclose = function() { |
|
console.log('Notification closed:', title); |
|
}; |
|
} |
|
|
|
return notification; |
|
} catch (error) { |
|
console.error('Error showing notification:', error); |
|
return null; |
|
} |
|
} |
|
|
|
|
|
function scheduleSystemNotification(medication) { |
|
if (!notificationPermissionGranted) return; |
|
|
|
|
|
if (scheduledNotifications[medication.id]) { |
|
clearTimeout(scheduledNotifications[medication.id]); |
|
delete scheduledNotifications[medication.id]; |
|
} |
|
|
|
|
|
const [hours, minutes] = medication.time.split(':').map(Number); |
|
const now = new Date(); |
|
const notificationTime = new Date(); |
|
notificationTime.setHours(hours); |
|
notificationTime.setMinutes(minutes); |
|
notificationTime.setSeconds(0); |
|
|
|
|
|
if (notificationTime < now) { |
|
notificationTime.setDate(notificationTime.getDate() + 1); |
|
} |
|
|
|
const timeUntilNotification = notificationTime.getTime() - now.getTime(); |
|
|
|
if (timeUntilNotification > 0 && timeUntilNotification < 24 * 60 * 60 * 1000) { |
|
scheduledNotifications[medication.id] = setTimeout(() => { |
|
const title = `Time to take ${medication.name}`; |
|
const options = { |
|
body: `Dosage: ${medication.dosage}\n${medication.note || ''}`, |
|
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png', |
|
tag: `medication-${medication.id}`, |
|
requireInteraction: true, |
|
vibrate: [200, 100, 200], |
|
actions: [ |
|
{ |
|
action: 'mark-taken', |
|
title: 'Mark as Taken', |
|
icon: 'https://cdn-icons-png.flaticon.com/512/1828/1828640.png' |
|
} |
|
] |
|
}; |
|
|
|
const notif = showSystemNotification(title, options); |
|
|
|
if (notif) { |
|
notif.onclick = (event) => { |
|
|
|
if (event.action === 'mark-taken') { |
|
takeMedication(medication.id); |
|
} else { |
|
|
|
window.focus(); |
|
} |
|
notif.close(); |
|
}; |
|
} |
|
|
|
|
|
setTimeout(() => { |
|
if (notif) notif.close(); |
|
}, 5 * 60 * 1000); |
|
|
|
}, timeUntilNotification); |
|
} |
|
} |
|
|
|
|
|
function renderMedications() { |
|
medicationList.innerHTML = ''; |
|
|
|
|
|
const sortedMeds = [...medications].sort((a, b) => { |
|
return a.time.localeCompare(b.time); |
|
}); |
|
|
|
const upcomingMeds = sortedMeds.filter(med => !med.isTaken); |
|
|
|
if (upcomingMeds.length === 0) { |
|
emptyState.style.display = 'block'; |
|
medicationList.style.display = 'none'; |
|
} else { |
|
emptyState.style.display = 'none'; |
|
medicationList.style.display = 'flex'; |
|
|
|
upcomingMeds.forEach((med, index) => { |
|
const medItem = document.createElement('div'); |
|
medItem.className = 'medication-item animate-on-scroll'; |
|
medItem.dataset.id = med.id; |
|
medItem.style.transitionDelay = `${index * 0.1}s`; |
|
|
|
const [hours, minutes] = med.time.split(':'); |
|
const hour = parseInt(hours); |
|
const amPm = hour >= 12 ? 'PM' : 'AM'; |
|
const hour12 = hour % 12 || 12; |
|
const timeDisplay = `${hour12}:${minutes} ${amPm}`; |
|
|
|
|
|
const now = new Date(); |
|
const medTime = new Date(); |
|
medTime.setHours(hour); |
|
medTime.setMinutes(minutes); |
|
|
|
const timeDiff = (medTime - now) / (1000 * 60 * 60); |
|
let urgencyClass = ''; |
|
|
|
if (timeDiff < 0) { |
|
|
|
urgencyClass = 'past-due'; |
|
} else if (timeDiff < 1) { |
|
|
|
urgencyClass = 'due-soon'; |
|
} |
|
|
|
medItem.innerHTML = ` |
|
<div class="med-icon"> |
|
<i class="fas fa-pills"></i> |
|
</div> |
|
<div class="med-details"> |
|
<h3>${med.name}</h3> |
|
<p>${med.dosage} • ${med.category || 'General'}</p> |
|
${med.note ? `<div class="med-note"><i class="fas fa-info-circle"></i> ${med.note}</div>` : ''} |
|
</div> |
|
<div class="med-time ${urgencyClass}"> |
|
<p>${timeDisplay} ${urgencyClass === 'past-due' ? ' (Past Due)' : urgencyClass === 'due-soon' ? ' (Soon!)' : ''}</p> |
|
<button class="btn btn-success take-btn"> |
|
<i class="fas fa-check"></i> Mark Taken |
|
</button> |
|
</div> |
|
`; |
|
|
|
if (urgencyClass === 'due-soon' || urgencyClass === 'past-due') { |
|
medItem.style.animation = urgencyClass === 'past-due' ? 'pulse 2s infinite' : 'pulse 3s infinite'; |
|
} |
|
|
|
medicationList.appendChild(medItem); |
|
|
|
|
|
medItem.querySelector('.take-btn').addEventListener('click', () => takeMedication(med.id)); |
|
}); |
|
|
|
|
|
setTimeout(() => { |
|
const observer = new IntersectionObserver((entries) => { |
|
entries.forEach(entry => { |
|
if (entry.isIntersecting) { |
|
entry.target.classList.add('show'); |
|
} |
|
}); |
|
}, { threshold: 0.1 }); |
|
|
|
document.querySelectorAll('.animate-on-scroll').forEach(el => { |
|
observer.observe(el); |
|
}); |
|
}, 100); |
|
} |
|
} |
|
|
|
|
|
function takeMedication(id) { |
|
const index = medications.findIndex(med => med.id === id); |
|
if (index !== -1) { |
|
medications[index].isTaken = true; |
|
medications[index].lastTaken = new Date().toISOString(); |
|
renderMedications(); |
|
updateStats(); |
|
showNotification(`Marked ${medications[index].name} as taken.`, 'success'); |
|
|
|
|
|
if (scheduledNotifications[id]) { |
|
clearTimeout(scheduledNotifications[id]); |
|
delete scheduledNotifications[id]; |
|
} |
|
|
|
|
|
if (notificationPermissionGranted) { |
|
scheduleSystemNotification(medications[index]); |
|
} |
|
} |
|
} |
|
|
|
|
|
function showNotification(message, type = 'success') { |
|
|
|
notification.className = `notification ${type}`; |
|
notificationText.textContent = message; |
|
notification.classList.add('show'); |
|
|
|
setTimeout(() => { |
|
notification.classList.remove('show'); |
|
}, 3000); |
|
|
|
|
|
if (notificationPermissionGranted && (type === 'warning' || type === 'error')) { |
|
try { |
|
const notification = new Notification('MedTrack Alert', { |
|
body: message, |
|
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png', |
|
}); |
|
|
|
setTimeout(() => notification.close(), 5000); |
|
} catch (e) { |
|
console.warn('Could not show system notification', e); |
|
} |
|
} |
|
} |
|
|
|
|
|
function checkNotifications() { |
|
const now = new Date(); |
|
const currentHours = now.getHours().toString().padStart(2, '0'); |
|
const currentMinutes = now.getMinutes().toString().padStart(2, '0'); |
|
const currentTime = `${currentHours}:${currentMinutes}`; |
|
|
|
medications.forEach(med => { |
|
if (!med.isTaken && med.time === currentTime) { |
|
const message = `Time to take ${med.name} (${med.dosage})${med.note ? ': ' + med.note : ''}`; |
|
showNotification(message, 'warning'); |
|
|
|
|
|
if (notificationPermissionGranted) { |
|
try { |
|
const notification = new Notification(`Time to take ${med.name}`, { |
|
body: message, |
|
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png', |
|
requireInteraction: true, |
|
vibrate: [200, 100, 200] |
|
}); |
|
|
|
|
|
setTimeout(() => notification.close(), 5 * 60 * 1000); |
|
} catch (e) { |
|
console.warn('Could not show system notification', e); |
|
} |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
function scheduleNextNotification(medication) { |
|
|
|
if (notificationPermissionGranted) { |
|
scheduleSystemNotification(medication); |
|
} |
|
} |
|
|
|
|
|
function updateStats() { |
|
const total = medications.length; |
|
const upcoming = medications.filter(med => !med.isTaken).length; |
|
const taken = medications.filter(med => med.isTaken).length; |
|
const adherence = total > 0 ? Math.round((taken / total) * 100) : 0; |
|
|
|
|
|
animateValue('upcomingCount', parseInt(upcomingCount.textContent), upcoming, 500); |
|
animateValue('totalMeds', parseInt(totalMeds.textContent), total, 500); |
|
animateValue('adherenceRate', parseInt(adherenceRate.textContent.replace('%', '')), adherence, 500); |
|
} |
|
|
|
|
|
function animateValue(id, start, end, duration) { |
|
const obj = document.getElementById(id); |
|
let startTimestamp = null; |
|
const step = (timestamp) => { |
|
if (!startTimestamp) startTimestamp = timestamp; |
|
const progress = Math.min((timestamp - startTimestamp) / duration, 1); |
|
const val = Math.floor(progress * (end - start) + start); |
|
obj.textContent = id === 'adherenceRate' ? `${val}%` : val; |
|
if (progress < 1) { |
|
window.requestAnimationFrame(step); |
|
} |
|
}; |
|
window.requestAnimationFrame(step); |
|
} |
|
|
|
|
|
function showModal() { |
|
addMedModal.style.display = 'flex'; |
|
document.body.style.overflow = 'hidden'; |
|
setTimeout(() => addMedModal.classList.add('show'), 10); |
|
} |
|
|
|
|
|
function hideModal() { |
|
addMedModal.classList.remove('show'); |
|
setTimeout(() => { |
|
addMedModal.style.display = 'none'; |
|
document.body.style.overflow = 'auto'; |
|
medicationForm.reset(); |
|
while (timeInputs.children.length > 1) { |
|
timeInputs.removeChild(timeInputs.lastChild); |
|
} |
|
}, 300); |
|
} |
|
|
|
|
|
function addMedication(name, dosage, frequency, times, note) { |
|
const categories = ["Pain Relief", "Supplement", "Antibiotic", "Cardiac", "Gastrointestinal", "Other"]; |
|
const randomCategory = categories[Math.floor(Math.random() * categories.length)]; |
|
|
|
times.forEach(time => { |
|
const newMed = { |
|
id: nextId++, |
|
name, |
|
dosage, |
|
time, |
|
isTaken: false, |
|
note, |
|
category: randomCategory, |
|
lastTaken: null |
|
}; |
|
|
|
medications.push(newMed); |
|
|
|
|
|
if (notificationPermissionGranted) { |
|
scheduleSystemNotification(newMed); |
|
} |
|
}); |
|
|
|
renderMedications(); |
|
updateStats(); |
|
showNotification(`Added ${name} to your medications.`, 'success'); |
|
hideModal(); |
|
} |
|
|
|
|
|
function setupIntersectionObserver() { |
|
const observer = new IntersectionObserver((entries) => { |
|
entries.forEach(entry => { |
|
if (entry.isIntersecting) { |
|
entry.target.classList.add('show'); |
|
} |
|
}); |
|
}, { threshold: 0.1 }); |
|
|
|
document.querySelectorAll('.animate-on-scroll').forEach(el => { |
|
observer.observe(el); |
|
}); |
|
} |
|
|
|
|
|
addMedBtn.addEventListener('click', showModal); |
|
addFirstMed.addEventListener('click', showModal); |
|
closeModal.addEventListener('click', hideModal); |
|
cancelBtn.addEventListener('click', hideModal); |
|
|
|
|
|
medFrequency.addEventListener('change', function() { |
|
const count = this.value === 'once' ? 1 : |
|
this.value === 'twice' ? 2 : |
|
this.value === 'thrice' ? 3 : 1; |
|
|
|
|
|
while (timeInputs.children.length > 1) { |
|
timeInputs.removeChild(timeInputs.lastChild); |
|
} |
|
|
|
|
|
for (let i = 1; i < count; i++) { |
|
const div = document.createElement('div'); |
|
div.innerHTML = `<input type="time" id="time${i+1}" class="form-control" required>`; |
|
timeInputs.appendChild(div); |
|
} |
|
}); |
|
|
|
|
|
medicationForm.addEventListener('submit', function(e) { |
|
e.preventDefault(); |
|
|
|
const name = document.getElementById('medName').value; |
|
const dosage = document.getElementById('medDosage').value; |
|
const frequency = document.getElementById('medFrequency').value; |
|
const note = document.getElementById('medNotes').value; |
|
|
|
|
|
const times = []; |
|
const timeInputElements = timeInputs.querySelectorAll('input[type="time"]'); |
|
timeInputElements.forEach(input => { |
|
if (input.value) times.push(input.value); |
|
}); |
|
|
|
if (times.length === 0) { |
|
showNotification('Please enter at least one time', 'error'); |
|
return; |
|
} |
|
|
|
addMedication(name, dosage, frequency, times, note); |
|
}); |
|
|
|
|
|
addMedModal.addEventListener('click', function(e) { |
|
if (e.target === addMedModal) { |
|
hideModal(); |
|
} |
|
}); |
|
|
|
|
|
addFirstMed.addEventListener('click', showModal); |
|
|
|
|
|
init(); |
|
|
|
|
|
const style = document.createElement('style'); |
|
style.textContent = ` |
|
@keyframes pulse { |
|
0% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.4); } |
|
70% { box-shadow: 0 0 0 10px rgba(231, 76, 60, 0); } |
|
100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0); } |
|
} |
|
|
|
.due-soon p, .past-due p { |
|
color: #e74c3c !important; |
|
} |
|
`; |
|
document.head.appendChild(style); |
|
</script> |
|
</body> |
|
</html> |