Family Medical App and medication and appointment reminders
<title>Family Medical Survival System</title><!-- PWA Configuration -->
<meta name="theme-color" content="#667eea">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Family Medical">
<meta name="mobile-web-app-capable" content="yes">
<!-- PWA Manifest -->
<link rel="manifest" id="pwa-manifest">
<!-- QR Code Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
padding: 30px 0;
}
.header h1 {
font-size: 2.5rem;
font-weight: 300;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.login-section {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 25px;
margin-bottom: 30px;
color: white;
text-align: center;
}
.family-member-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin: 20px 0;
}
.member-card {
padding: 20px;
background: rgba(255,255,255,0.2);
border: none;
border-radius: 15px;
color: white;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
text-align: left;
}
.member-card:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
}
.member-card.active {
background: rgba(255,255,255,0.4);
border: 2px solid white;
}
.member-card.new-member {
border: 2px dashed rgba(255,255,255,0.6);
display: flex;
align-items: center;
justify-content: center;
min-height: 100px;
}
.add-member-btn {
background: rgba(46, 204, 113, 0.8);
border: none;
color: white;
padding: 15px 30px;
border-radius: 25px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
margin: 10px;
transition: all 0.3s ease;
}
.add-member-btn:hover {
background: rgba(46, 204, 113, 1);
transform: translateY(-2px);
}
.privacy-controls {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px;
margin-bottom: 30px;
color: white;
}
.privacy-toggle {
display: flex;
align-items: center;
margin: 10px 0;
padding: 10px;
background: rgba(255,255,255,0.1);
border-radius: 10px;
}
.toggle-switch {
position: relative;
width: 60px;
height: 30px;
background: rgba(255,255,255,0.3);
border-radius: 15px;
margin-right: 15px;
cursor: pointer;
transition: all 0.3s ease;
}
.toggle-switch.active {
background: #2ecc71;
}
.toggle-slider {
position: absolute;
top: 3px;
left: 3px;
width: 24px;
height: 24px;
background: white;
border-radius: 50%;
transition: all 0.3s ease;
}
.toggle-switch.active .toggle-slider {
transform: translateX(30px);
}
.system-status {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px;
margin-bottom: 30px;
color: white;
text-align: center;
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
}
.status-item {
background: rgba(255,255,255,0.2);
padding: 15px;
border-radius: 10px;
text-align: center;
}
.status-online {
color: #4CAF50;
}
.main-content {
background: rgba(255,255,255,0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
}
.profile-header {
display: flex;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #eee;
}
.profile-icon {
font-size: 3rem;
margin-right: 20px;
}
.profile-info h2 {
color: #2c3e50;
margin-bottom: 5px;
}
.profile-status {
color: #7f8c8d;
font-size: 0.9rem;
}
.info-sections {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 25px;
}
.info-section {
background: #f8f9fa;
padding: 20px;
border-radius: 15px;
border-left: 4px solid #3498db;
}
.section-title {
font-size: 1.2rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.section-icon {
margin-right: 10px;
font-size: 1.5rem;
}
.info-item {
margin-bottom: 10px;
padding: 10px;
background: white;
border-radius: 8px;
border-left: 3px solid #ecf0f1;
}
.info-label {
font-weight: 600;
color: #34495e;
margin-bottom: 5px;
}
.info-value {
color: #7f8c8d;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 30px;
padding-top: 20px;
border-top: 2px solid #eee;
}
.action-button {
padding: 12px 24px;
background: linear-gradient(45deg, #3498db, #2980b9);
color: white;
border: none;
border-radius: 25px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
}
.action-button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(52, 152, 219, 0.6);
}
.action-button.emergency {
background: linear-gradient(45deg, #e74c3c, #c0392b);
box-shadow: 0 5px 15px rgba(231, 76, 60, 0.4);
}
.action-button.success {
background: linear-gradient(45deg, #27ae60, #229954);
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.4);
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
z-index: 2000;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
border-radius: 20px;
padding: 30px;
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2c3e50;
}
.form-input, .form-textarea, .form-select {
width: 100%;
padding: 12px;
border: 2px solid #ecf0f1;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-input:focus, .form-textarea:focus, .form-select:focus {
outline: none;
border-color: #3498db;
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
z-index: 3000;
max-width: 350px;
transform: translateX(400px);
transition: all 0.3s ease;
}
.notification.show {
transform: translateX(0);
}
.notification.success {
border-left: 4px solid #27ae60;
}
.notification.error {
border-left: 4px solid #e74c3c;
}
.notification.info {
border-left: 4px solid #3498db;
}
.welcome-screen {
text-align: center;
padding: 50px 20px;
color: #7f8c8d;
}
.welcome-icon {
font-size: 4rem;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.container { padding: 15px; }
.header h1 { font-size: 2rem; }
.info-sections { grid-template-columns: 1fr; }
.family-member-list { grid-template-columns: 1fr; }
.action-buttons { flex-direction: column; }
}
</style>
<!-- Login Section -->
<div class="login-section" id="login-section">
<h3>π¨βπ©βπ§βπ¦ Select Your Profile</h3>
<div class="family-member-list" id="family-member-list">
<!-- Family members will be populated here -->
</div>
<button class="add-member-btn" onclick="showAddMemberModal()">β Add New Family Member</button>
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.3);">
<h4>π Privacy Settings</h4>
<div class="privacy-controls">
<div class="privacy-toggle">
<div class="toggle-switch" id="appointments-privacy" onclick="togglePrivacy('appointments')">
<div class="toggle-slider"></div>
</div>
<label>Share appointments with family</label>
</div>
<div class="privacy-toggle">
<div class="toggle-switch" id="medications-privacy" onclick="togglePrivacy('medications')">
<div class="toggle-slider"></div>
</div>
<label>Share medication reminders</label>
</div>
<div class="privacy-toggle">
<div class="toggle-switch active" id="emergency-privacy" onclick="togglePrivacy('emergency')">
<div class="toggle-slider"></div>
</div>
<label>Emergency contact sharing (always on)</label>
</div>
</div>
</div>
</div>
<!-- System Status -->
<div class="system-status">
<h3>π System Status</h3>
<div class="status-grid">
<div class="status-item">
<div class="status-online">β</div>
<div>Family System Online</div>
</div>
<div class="status-item">
<div id="member-count">π₯ 0 Members</div>
</div>
<div class="status-item">
<div>π Privacy Protected</div>
</div>
<div class="status-item">
<div>π± Mobile Ready</div>
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="main-content" id="main-content" style="display: none;">
<!-- Profile content will be loaded here -->
</div>
<!-- Welcome Screen -->
<div class="welcome-screen" id="welcome-screen">
<div class="welcome-icon">π</div>
<h2>Welcome to Your Family Medical System</h2>
<p>Select a family member above to view their medical information, or add a new member to get started.</p>
<p style="margin-top: 20px; font-size: 0.9rem; opacity: 0.8;">
π All information is kept private and only visible to the individual unless they choose to share specific details.
</p>
</div>
</div>
<!-- Add Member Modal -->
<div id="add-member-modal" class="modal">
<div class="modal-content">
<h2>β Add New Family Member</h2>
<div class="form-group">
<label class="form-label">π€ Name:</label>
<input type="text" id="new-member-name" class="form-input" placeholder="Enter name">
</div>
<div class="form-group">
<label class="form-label">π Role:</label>
<select id="new-member-role" class="form-select">
<option value="">Select role...</option>
<option value="parent">π¨βπ©βπ§βπ¦ Parent</option>
<option value="child">π§π¦ Child</option>
<option value="grandparent">π΄π΅ Grandparent</option>
<option value="sibling">π« Sibling</option>
<option value="spouse">π Spouse/Partner</option>
<option value="other">π€ Other Family</option>
</select>
</div>
<div class="form-group">
<label class="form-label">π§ Email (for login):</label>
<input type="email" id="new-member-email" class="form-input" placeholder="email@example.com">
</div>
<div class="action-buttons">
<button class="action-button success" onclick="addFamilyMember()">β
Add Member</button>
<button class="action-button" onclick="closeAddMemberModal()">β Cancel</button>
</div>
</div>
</div>
<!-- Edit Profile Modal -->
<div id="edit-profile-modal" class="modal">
<div class="modal-content">
<h2 id="edit-profile-title">βοΈ Edit Profile</h2>
<div class="form-group">
<label class="form-label">π€ Name:</label>
<input type="text" id="edit-name" class="form-input">
</div>
<div class="form-group">
<label class="form-label">π₯ Medical Conditions:</label>
<textarea id="edit-conditions" class="form-textarea" placeholder="List any medical conditions..."></textarea>
</div>
<div class="form-group">
<label class="form-label">β οΈ Allergies:</label>
<input type="text" id="edit-allergies" class="form-input" placeholder="e.g., Penicillin, Nuts, or 'None Known'">
</div>
<div class="form-group">
<label class="form-label">π Current Medications:</label>
<textarea id="edit-medications" class="form-textarea" placeholder="List medications, dosage, and timing..."></textarea>
</div>
<div class="form-group">
<label class="form-label">π¨ββοΈ Primary Doctor:</label>
<input type="text" id="edit-doctor" class="form-input" placeholder="Dr. Name - Phone Number">
</div>
<div class="form-group">
<label class="form-label">π Emergency Contact:</label>
<input type="text" id="edit-emergency" class="form-input" placeholder="Name - Phone Number">
</div>
<div class="form-group">
<label class="form-label">π©Έ Blood Type:</label>
<select id="edit-bloodtype" class="form-select">
<option value="">Unknown</option>
<option value="A+">A+</option>
<option value="A-">A-</option>
<option value="B+">B+</option>
<option value="B-">B-</option>
<option value="AB+">AB+</option>
<option value="AB-">AB-</option>
<option value="O+">O+</option>
<option value="O-">O-</option>
</select>
</div>
<div class="form-group">
<label class="form-label">π
Upcoming Appointments:</label>
<textarea id="edit-appointments" class="form-textarea" placeholder="List upcoming appointments..."></textarea>
</div>
<div class="action-buttons">
<button class="action-button success" onclick="saveProfile()">πΎ Save Changes</button>
<button class="action-button" onclick="closeEditProfileModal()">β Cancel</button>
<button class="action-button emergency" onclick="deleteMember()">ποΈ Remove Member</button>
</div>
</div>
</div>
<!-- QR Code Modal -->
<div id="qr-modal" class="modal">
<div class="modal-content" style="text-align: center;">
<h2 id="qr-title">π± Emergency QR Code</h2>
<div id="qr-code" style="margin: 20px 0;"></div>
<p id="qr-instructions">Show this QR code to emergency responders</p>
<div class="action-buttons" style="justify-content: center;">
<button class="action-button" onclick="closeQRModal()">Close</button>
</div>
</div>
</div>
<script>
// Family data structure - using memory storage for this environment
let familyData = {};
let currentUser = null;
let privacySettings = {
appointments: false,
medications: false,
emergency: true
};
// Initialize the application
function init() {
loadFamilyData();
updateMemberList();
updatePrivacyToggles();
showNotification('Family Medical System ready!', 'success');
}
// Family member management
function loadFamilyData() {
// In a real app, this would load from secure storage
if (Object.keys(familyData).length === 0) {
// Add sample data for demonstration
familyData['sample-member'] = {
id: 'sample-member',
name: 'Sample Member',
role: 'parent',
email: 'sample@family.com',
conditions: '',
allergies: '',
medications: '',
doctor: '',
emergency: '',
bloodType: '',
appointments: '',
lastLogin: new Date().toISOString()
};
}
}
function saveFamilyData() {
// In a real app, this would save to secure storage
console.log('Family data saved to memory');
}
function updateMemberList() {
const memberList = document.getElementById('family-member-list');
memberList.innerHTML = '';
Object.values(familyData).forEach(member => {
const memberCard = document.createElement('div');
memberCard.className = 'member-card';
memberCard.onclick = () => selectMember(member.id);
const roleIcon = getRoleIcon(member.role);
const lastSeen = member.lastLogin ? new Date(member.lastLogin).toLocaleDateString() : 'Never';
memberCard.innerHTML = `
<div style="font-size: 1.5rem; margin-bottom: 5px;">${roleIcon}</div>
<div style="font-weight: bold;">${member.name}</div>
<div style="font-size: 0.8rem; opacity: 0.8;">${member.role}</div>
<div style="font-size: 0.7rem; opacity: 0.6;">Last active: ${lastSeen}</div>
`;
memberList.appendChild(memberCard);
});
// Update member count
document.getElementById('member-count').textContent = `π₯ ${Object.keys(familyData).length} Members`;
}
function getRoleIcon(role) {
const icons = {
'parent': 'π¨βπ©βπ§βπ¦',
'child': 'π§',
'grandparent': 'π΄',
'sibling': 'π«',
'spouse': 'π',
'other': 'π€'
};
return icons[role] || 'π€';
}
function selectMember(memberId) {
currentUser = memberId;
const member = familyData[memberId];
if (!member) {
showNotification('Member not found', 'error');
return;
}
// Update last login
member.lastLogin = new Date().toISOString();
saveFamilyData();
// Hide login section and welcome screen
document.getElementById('login-section').style.display = 'none';
document.getElementById('welcome-screen').style.display = 'none';
document.getElementById('main-content').style.display = 'block';
// Load member profile
loadMemberProfile(member);
// Update member list to show active state
document.querySelectorAll('.member-card').forEach(card => {
card.classList.remove('active');
});
event.target.closest('.member-card').classList.add('active');
showNotification(`Welcome back, ${member.name}!`, 'success');
}
function loadMemberProfile(member) {
const mainContent = document.getElementById('main-content');
const roleIcon = getRoleIcon(member.role);
mainContent.innerHTML = `
<div class="profile-header">
<div class="profile-icon">${roleIcon}</div>
<div class="profile-info">
<h2>${member.name}</h2>
<div class="profile-status">Family ${member.role} β’ Last updated: ${new Date(member.lastLogin).toLocaleDateString()}</div>
</div>
</div>
<div class="info-sections">
<div class="info-section">
<div class="section-title">
<span class="section-icon">π₯</span>
Medical Information
</div>
<div class="info-item">
<div class="info-label">Conditions</div>
<div class="info-value">${member.conditions || 'None specified'}</div>
</div>
<div class="info-item">
<div class="info-label">Allergies</div>
<div class="info-value">${member.allergies || 'None known'}</div>
</div>
<div class="info-item">
<div class="info-label">Blood Type</div>
<div class="info-value">${member.bloodType || 'Unknown'}</div>
</div>
</div>
<div class="info-section">
<div class="section-title">
<span class="section-icon">π</span>
Medications
</div>
<div class="info-item">
<div class="info-label">Current Medications</div>
<div class="info-value">${member.medications || 'None specified'}</div>
</div>
</div>
<div class="info-section">
<div class="section-title">
<span class="section-icon">π¨ββοΈ</span>
Healthcare Team
</div>
<div class="info-item">
<div class="info-label">Primary Doctor</div>
<div class="info-value">${member.doctor || 'Not specified'}</div>
</div>
<div class="info-item">
<div class="info-label">Emergency Contact</div>
<div class="info-value">${member.emergency || 'Not specified'}</div>
</div>
</div>
<div class="info-section">
<div class="section-title">
<span class="section-icon">π
</span>
Appointments
</div>
<div class="info-item">
<div class="info-label">Upcoming</div>
<div class="info-value">${member.appointments || 'No appointments scheduled'}</div>
</div>
</div>
</div>
<div class="action-buttons">
<button class="action-button" onclick="editProfile()">βοΈ Edit My Information</button>
<button class="action-button emergency" onclick="generateEmergencyQR()">π± Emergency QR Code</button>
<button class="action-button" onclick="logOut()">πͺ Switch User</button>
</div>
`;
}
// Member management functions
function showAddMemberModal() {
document.getElementById('add-member-modal').style.display = 'flex';
}
function closeAddMemberModal() {
document.getElementById('add-member-modal').style.display = 'none';
clearAddMemberForm();
}
function clearAddMemberForm() {
document.getElementById('new-member-name').value = '';
document.getElementById('new-member-role').value = '';
document.getElementById('new-member-email').value = '';
}
function addFamilyMember() {
const name = document.getElementById('new-member-name').value.trim();
const role = document.getElementById('new-member-role').value;
const email = document.getElementById('new-member-email').value.trim();
if (!name || !role || !email) {
showNotification('Please fill in all fields', 'error');
return;
}
// Generate unique ID
const memberId = 'member-' + Date.now();
// Create new member
familyData[memberId] = {
id: memberId,
name: name,
role: role,
email: email,
conditions: '',
allergies: '',
medications: '',
doctor: '',
emergency: '',
bloodType: '',
appointments: '',
lastLogin: new Date().toISOString()
};
saveFamilyData();
updateMemberList();
closeAddMemberModal();
showNotification(`${name} added to family!`, 'success');
}
function editProfile() {
if (!currentUser) return;
const member = familyData[currentUser];
document.getElementById('edit-profile-title').textContent = `Edit ${member.name}'s Profile`;
// Pre-fill form
document.getElementById('edit-name').value = member.name || '';
document.getElementById('edit-conditions').value = member.conditions || '';
document.getElementById('edit-allergies').value = member.allergies || '';
document.getElementById('edit-medications').value = member.medications || '';
document.getElementById('edit-doctor').value = member.doctor || '';
document.getElementById('edit-emergency').value = member.emergency || '';
document.getElementById('edit-bloodtype').value = member.bloodType || '';
document.getElementById('edit-appointments').value = member.appointments || '';
document.getElementById('edit-profile-modal').style.display = 'flex';
}
function closeEditProfileModal() {
document.getElementById('edit-profile-modal').style.display = 'none';
}
function saveProfile() {
if (!currentUser) return;
const member = familyData[currentUser];
// Update member data
member.name = document.getElementById('edit-name').value.trim();
member.conditions = document.getElementById('edit-conditions').value.trim();
member.allergies = document.getElementById('edit-allergies').value.trim();
member.medications = document.getElementById('edit-medications').value.trim();
member.doctor = document.getElementById('edit-doctor').value.trim();
member.emergency = document.getElementById('edit-emergency').value.trim();
member.bloodType = document.getElementById('edit-bloodtype').value;
member.appointments = document.getElementById('edit-appointments').value.trim();
member.lastLogin = new Date().toISOString();
saveFamilyData();
loadMemberProfile(member);
updateMemberList();
closeEditProfileModal();
showNotification('Profile updated successfully!', 'success');
}
function deleteMember() {
if (!currentUser) return;
const member = familyData[currentUser];
const confirmDelete = confirm(`Are you sure you want to remove ${member.name} from the family system? This cannot be undone.`);
if (confirmDelete) {
delete familyData[currentUser];
saveFamilyData();
updateMemberList();
logOut();
showNotification(`${member.name} removed from family system`, 'info');
}
}
function logOut() {
currentUser = null;
document.getElementById('login-section').style.display = 'block';
document.getElementById('main-content').style.display = 'none';
document.getElementById('welcome-screen').style.display = 'block';
// Remove active states
document.querySelectorAll('.member-card').forEach(card => {
card.classList.remove('active');
});
closeEditProfileModal();
closeQRModal();
}
// Privacy controls
function updatePrivacyToggles() {
Object.keys(privacySettings).forEach(setting => {
const toggle = document.getElementById(`${setting}-privacy`);
if (toggle) {
if (privacySettings[setting]) {
toggle.classList.add('active');
} else {
toggle.classList.remove('active');
}
}
});
}
function togglePrivacy(setting) {
if (setting === 'emergency') {
showNotification('Emergency sharing cannot be disabled for safety', 'info');
return;
}
privacySettings[setting] = !privacySettings[setting];
updatePrivacyToggles();
const status = privacySettings[setting] ? 'enabled' : 'disabled';
showNotification(`${setting} sharing ${status}`, 'info');
}
// QR Code generation
function generateEmergencyQR() {
if (!currentUser) return;
const member = familyData[currentUser];
// Prepare emergency data
const emergencyInfo = {
name: member.name,
role: member.role,
conditions: member.conditions || 'Not specified',
allergies: member.allergies || 'Not specified',
medications: member.medications || 'Not specified',
doctor: member.doctor || 'Not specified',
emergency_contact: member.emergency || 'Not specified',
blood_type: member.bloodType || 'Unknown',
generated: new Date().toISOString()
};
const qrData = `EMERGENCY MEDICAL INFO
Name:
// Clear previous QR code
const qrContainer = document.getElementById('qr-code');
qrContainer.innerHTML = '';
// Check if QRCode library is available
if (typeof QRCode === 'undefined') {
showNotification('QR Code library not available', 'error');
return;
}
try {
new QRCode(qrContainer, {
text: qrData,
width: 256,
height: 256,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
});
document.getElementById('qr-title').textContent = `Emergency QR Code for ${member.name}`;
document.getElementById('qr-instructions').textContent = 'Show this QR code to emergency responders for instant access to medical information';
document.getElementById('qr-modal').style.display = 'flex';
showNotification('Emergency QR code generated', 'success');
} catch (error) {
showNotification('Error generating QR code', 'error');
console.error('QR Code generation error:', error);
}
}
function closeQRModal() {
document.getElementById('qr-modal').style.display = 'none';
}
// Notification system
function showNotification(message, type = 'info') {
// Remove existing notifications
document.querySelectorAll('.notification').forEach(notification => {
notification.remove();
});
const notification = document.createElement('div');
notification.className = `notification ${type} show`;
const icons = {
success: 'β
',
error: 'β',
info: 'βΉοΈ',
warning: 'β οΈ'
};
notification.innerHTML = `
<div style="font-weight: bold; margin-bottom: 8px;">
${icons[type] || icons.info} ${type.charAt(0).toUpperCase() + type.slice(1)}
</div>
<div>${message}</div>
`;
document.body.appendChild(notification);
// Auto-remove after 4 seconds
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (document.body.contains(notification)) {
document.body.removeChild(notification);
}
}, 300);
}, 4000);
}
// Modal management
function closeModalOnOutsideClick(event) {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
}
// Keyboard shortcuts
function handleKeyboardShortcuts(event) {
if (event.key === 'Escape') {
closeEditProfileModal();
closeAddMemberModal();
closeQRModal();
}
}
// Family sharing functions
function getSharedAppointments() {
if (!privacySettings.appointments) return {};
const shared = {};
Object.values(familyData).forEach(member => {
if (member.appointments) {
shared[member.name] = member.appointments;
}
});
return shared;
}
function getSharedMedications() {
if (!privacySettings.medications) return {};
const shared = {};
Object.values(familyData).forEach(member => {
if (member.medications) {
shared[member.name] = member.medications;
}
});
return shared;
}
function getFamilyEmergencyContacts() {
const contacts = {};
Object.values(familyData).forEach(member => {
if (member.emergency) {
contacts[member.name] = member.emergency;
}
});
return contacts;
}
// Search and filter functions
function searchFamily(query) {
const results = [];
const searchTerm = query.toLowerCase();
Object.values(familyData).forEach(member => {
if (member.name.toLowerCase().includes(searchTerm) ||
member.role.toLowerCase().includes(searchTerm) ||
member.conditions.toLowerCase().includes(searchTerm)) {
results.push(member);
}
});
return results;
}
// Data export/import functions
function exportFamilyData() {
const exportData = {
familyData: familyData,
privacySettings: privacySettings,
exportDate: new Date().toISOString(),
version: '2.0'
};
const dataStr = JSON.stringify(exportData, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});
const link = document.createElement('a');
link.href = URL.createObjectURL(dataBlob);
link.download = `family-medical-data-${new Date().toISOString().split('T')[0]}.json`;
link.click();
showNotification('Family data exported successfully', 'success');
}
function importFamilyData(fileInput) {
const file = fileInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const importData = JSON.parse(e.target.result);
if (importData.familyData) {
familyData = importData.familyData;
}
if (importData.privacySettings) {
privacySettings = importData.privacySettings;
}
saveFamilyData();
updateMemberList();
updatePrivacyToggles();
showNotification('Family data imported successfully', 'success');
} catch (error) {
showNotification('Error importing data: Invalid file format', 'error');
}
};
reader.readAsText(file);
}
// Emergency functions
function triggerFamilyEmergency() {
const emergencyContacts = getFamilyEmergencyContacts();
showNotification('Emergency alert sent to all family members', 'error');
// In a real app, this would send notifications to all family members
console.log('Emergency contacts:', emergencyContacts);
}
function checkMedicationReminders() {
const now = new Date();
const reminders = [];
Object.values(familyData).forEach(member => {
if (member.medications && privacySettings.medications) {
// Parse medication times and check for reminders
// This is a simplified version - real app would have proper scheduling
reminders.push(`${member.name}: Check medications`);
}
});
return reminders;
}
// Event listeners
// Note: PWA initialization moved to bottom of file
document.addEventListener('click', closeModalOnOutsideClick);
document.addEventListener('keydown', handleKeyboardShortcuts);
// Periodic updates
setInterval(() => {
const reminders = checkMedicationReminders();
if (reminders.length > 0 && currentUser) {
// Show medication reminders
console.log('Medication reminders:', reminders);
}
}, 60000); // Check every minute
// PWA support - only enable when served from a server
if ('serviceWorker' in navigator && window.location.protocol !== 'file:') {
window.addEventListener('load', () => {
// Register service worker inline
const swCode = `
const CACHE_NAME = 'family-medical-v1';
const urlsToCache = [
'/',
'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
});
`;
const blob = new Blob([swCode], { type: 'application/javascript' });
const swUrl = URL.createObjectURL(blob);
navigator.serviceWorker.register(swUrl)
.then(registration => {
console.log('PWA Service Worker registered successfully');
showNotification('App ready for offline use!', 'success');
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
});
}
// Create PWA manifest dynamically
function createPWAManifest() {
const manifest = {
"name": "Family Medical Survival System",
"short_name": "FamilyMed",
"description": "Private family medical management system",
"start_url": window.location.pathname,
"display": "standalone",
"background_color": "#667eea",
"theme_color": "#667eea",
"icons": [
{
"src": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3Eπ₯%3C/text%3E%3C/svg%3E",
"sizes": "192x192",
"type": "image/svg+xml"
},
{
"src": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3Eπ₯%3C/text%3E%3C/svg%3E",
"sizes": "512x512",
"type": "image/svg+xml"
}
],
"categories": ["medical", "health", "family"],
"lang": "en"
};
const manifestBlob = new Blob([JSON.stringify(manifest)], { type: 'application/json' });
const manifestUrl = URL.createObjectURL(manifestBlob);
document.getElementById('pwa-manifest').href = manifestUrl;
}
// Initialize PWA
document.addEventListener('DOMContentLoaded', function() {
createPWAManifest();
init();
});
// Auto-save functionality
let autoSaveTimeout;
function scheduleAutoSave() {
clearTimeout(autoSaveTimeout);
autoSaveTimeout = setTimeout(() => {
saveFamilyData();
console.log('Auto-saved family data');
}, 5000);
}
// Add auto-save to input events
document.addEventListener('input', scheduleAutoSave);
</script>