import React, { useState, useEffect } from 'react';
import { Heart, Utensils, Brain, Pill, Calendar, Plus, Activity, Download, Mail, Edit2, Trash2 } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
const ElderCareTracker = () => {
const [activeProfile, setActiveProfile] = useState(null);
const [profiles, setProfiles] = useState([]);
const [activeTab, setActiveTab] = useState('dashboard');
const [showAddForm, setShowAddForm] = useState(null);
const [showExportMenu, setShowExportMenu] = useState(false);
const [editingEntry, setEditingEntry] = useState(null);
const [entries, setEntries] = useState({
vitals: [],
meals: [],
mental: [],
medications: [],
notes: []
});
useEffect(() => {
loadData();
}, []);
useEffect(() => {
if (activeProfile) {
saveData();
}
}, [entries, profiles, activeProfile]);
const loadData = async () => {
try {
const profilesResult = await window.storage.get('elder-care-profiles');
if (profilesResult) {
const loadedProfiles = JSON.parse(profilesResult.value);
setProfiles(loadedProfiles);
if (loadedProfiles.length > 0) {
setActiveProfile(loadedProfiles[0].id);
await loadProfileData(loadedProfiles[0].id);
}
}
} catch (error) {
console.log('No existing profiles found');
}
};
const loadProfileData = async (profileId) => {
try {
const result = await window.storage.get(`elder-care-data-${profileId}`);
if (result) {
setEntries(JSON.parse(result.value));
} else {
setEntries({ vitals: [], meals: [], mental: [], medications: [], notes: [] });
}
} catch (error) {
setEntries({ vitals: [], meals: [], mental: [], medications: [], notes: [] });
}
};
const saveData = async () => {
if (!activeProfile) return;
try {
await window.storage.set('elder-care-profiles', JSON.stringify(profiles));
await window.storage.set(`elder-care-data-${activeProfile}`, JSON.stringify(entries));
} catch (error) {
console.error('Error saving:', error);
}
};
const addProfile = (name) => {
const newProfile = { id: Date.now().toString(), name, createdAt: new Date().toISOString() };
setProfiles([...profiles, newProfile]);
setActiveProfile(newProfile.id);
setEntries({ vitals: [], meals: [], mental: [], medications: [], notes: [] });
};
const addVital = (data) => {
const timestamp = data.date ? new Date(data.date + 'T' + new Date().toTimeString().split(' ')[0]).toISOString() : new Date().toISOString();
const newVital = { id: Date.now().toString(), timestamp, ...data };
delete newVital.date;
setEntries(prev => ({ ...prev, vitals: [newVital, ...prev.vitals] }));
setShowAddForm(null);
};
const addMeal = (data) => {
setEntries(prev => ({ ...prev, meals: [{ id: Date.now().toString(), timestamp: new Date().toISOString(), ...data }, ...prev.meals] }));
setShowAddForm(null);
};
const addMentalState = (data) => {
const timestamp = data.date ? new Date(data.date + 'T' + new Date().toTimeString().split(' ')[0]).toISOString() : new Date().toISOString();
const newMental = { id: Date.now().toString(), timestamp, ...data };
delete newMental.date;
setEntries(prev => ({ ...prev, mental: [newMental, ...prev.mental] }));
setShowAddForm(null);
};
const addMedication = (data) => {
const timestamp = data.date ? new Date(data.date + 'T' + new Date().toTimeString().split(' ')[0]).toISOString() : new Date().toISOString();
const newMed = { id: Date.now().toString(), timestamp, ...data };
delete newMed.date;
setEntries(prev => ({ ...prev, medications: [newMed, ...prev.medications] }));
setShowAddForm(null);
};
const addNote = (data) => {
setEntries(prev => ({ ...prev, notes: [{ id: Date.now().toString(), timestamp: new Date().toISOString(), ...data }, ...prev.notes] }));
setShowAddForm(null);
};
const updateEntry = (type, updatedData) => {
setEntries(prev => ({ ...prev, [type]: prev[type].map(entry => entry.id === updatedData.id ? updatedData : entry) }));
setEditingEntry(null);
setShowAddForm(null);
};
const deleteEntry = (type, id) => {
if (confirm('Delete this entry?')) {
setEntries(prev => ({ ...prev, [type]: prev[type].filter(entry => entry.id !== id) }));
}
};
const formatDate = (isoString) => {
const date = new Date(isoString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};
const formatChartDate = (isoString) => {
const date = new Date(isoString);
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
};
const getActiveProfileName = () => {
const profile = profiles.find(p => p.id === activeProfile);
return profile ? profile.name : '';
};
const exportData = () => {
const dataStr = JSON.stringify({ profile: getActiveProfileName(), exportDate: new Date().toISOString(), data: entries }, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `elder-care-${getActiveProfileName().replace(/\s+/g, '-')}-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
setShowExportMenu(false);
};
const exportCSV = () => {
const profileName = getActiveProfileName();
let csv = `Elder Care Export - ${profileName}\n\n`;
if (entries.vitals.length > 0) {
csv += 'VITALS\nDate,Time,BP,Blood Sugar,HR,Temp,O2,Notes\n';
entries.vitals.forEach(v => {
const dt = new Date(v.timestamp);
const bp = (v.systolic && v.diastolic) ? `${v.systolic}/${v.diastolic}` : '';
csv += `${dt.toLocaleDateString()},${dt.toLocaleTimeString()},${bp},${v.bloodSugar||''},${v.heartRate||''},${v.temperature||''},${v.oxygen||''},"${(v.notes||'').replace(/"/g,'""')}"\n`;
});
csv += '\n';
}
if (entries.mental.length > 0) {
csv += 'MENTAL STATE\nDate,Time,Mood,Outburst,Subjects,Duration,Intensity,Notes\n';
entries.mental.forEach(m => {
const dt = new Date(m.timestamp);
const subj = (m.subjects && m.subjects.length) ? m.subjects.join('; ') : '';
csv += `${dt.toLocaleDateString()},${dt.toLocaleTimeString()},${m.mood}/10,${m.hasOutburst?'Yes':'No'},"${subj}",${m.duration||''},${m.intensity||''},"${(m.notes||'').replace(/"/g,'""')}"\n`;
});
csv += '\n';
}
if (entries.medications.length > 0) {
csv += 'MEDS\nDate,Time,Morning,Morning Status,Evening,Evening Status,Notes\n';
entries.medications.forEach(m => {
const dt = new Date(m.timestamp);
csv += `${dt.toLocaleDateString()},${dt.toLocaleTimeString()},${m.morningMeds?'Yes':'No'},${m.morningMeds?m.morningTakenAs:''},${m.eveningMeds?'Yes':'No'},${m.eveningMeds?m.eveningTakenAs:''},"${(m.notes||'').replace(/"/g,'""')}"\n`;
});
csv += '\n';
}
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `elder-care-${profileName.replace(/\s+/g, '-')}-${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
setShowExportMenu(false);
};
const emailData = () => {
const name = getActiveProfileName();
const body = `Elder Care Report for ${name}\n\nVitals: ${entries.vitals.length}\nMental: ${entries.mental.length}\nMeds: ${entries.medications.length}`;
window.location.href = `mailto:?subject=${encodeURIComponent('Elder Care Report - '+name)}&body=${encodeURIComponent(body)}`;
setShowExportMenu(false);
};
if (profiles.length === 0) {
return (
Elder Care Tracker
Create a profile to start
);
}
return (
Elder Care Tracker
{getActiveProfileName()}
{showExportMenu && (
)}
{[
{ id: 'dashboard', label: 'Dashboard', icon: Activity },
{ id: 'vitals', label: 'Vitals', icon: Heart },
{ id: 'meals', label: 'Meals', icon: Utensils },
{ id: 'mental', label: 'Mental', icon: Brain },
{ id: 'meds', label: 'Meds', icon: Pill },
{ id: 'notes', label: 'Notes', icon: Calendar }
].map(tab => {
const Icon = tab.icon;
return (
);
})}
{activeTab === 'dashboard' && }
{activeTab === 'vitals' && setShowAddForm('vitals')} onEdit={(e) => { setEditingEntry(e); setShowAddForm('vitals'); }} onDelete={(id) => deleteEntry('vitals', id)} formatDate={formatDate} />}
{activeTab === 'meals' && setShowAddForm('meals')} onEdit={(e) => { setEditingEntry(e); setShowAddForm('meals'); }} onDelete={(id) => deleteEntry('meals', id)} formatDate={formatDate} />}
{activeTab === 'mental' && setShowAddForm('mental')} onEdit={(e) => { setEditingEntry(e); setShowAddForm('mental'); }} onDelete={(id) => deleteEntry('mental', id)} formatDate={formatDate} />}
{activeTab === 'meds' && setShowAddForm('meds')} onEdit={(e) => { setEditingEntry(e); setShowAddForm('meds'); }} onDelete={(id) => deleteEntry('medications', id)} formatDate={formatDate} />}
{activeTab === 'notes' && setShowAddForm('notes')} onEdit={(e) => { setEditingEntry(e); setShowAddForm('notes'); }} onDelete={(id) => deleteEntry('notes', id)} formatDate={formatDate} />}
{showAddForm && (
{showAddForm === 'vitals' && updateEntry('vitals', d) : addVital} onCancel={() => { setShowAddForm(null); setEditingEntry(null); }} />}
{showAddForm === 'meals' && updateEntry('meals', d) : addMeal} onCancel={() => { setShowAddForm(null); setEditingEntry(null); }} />}
{showAddForm === 'mental' && updateEntry('mental', d) : addMentalState} onCancel={() => { setShowAddForm(null); setEditingEntry(null); }} />}
{showAddForm === 'meds' && updateEntry('medications', d) : addMedication} onCancel={() => { setShowAddForm(null); setEditingEntry(null); }} />}
{showAddForm === 'notes' && updateEntry('notes', d) : addNote} onCancel={() => { setShowAddForm(null); setEditingEntry(null); }} />}
)}
);
};
const ProfileForm = ({ onSubmit }) => {
const [name, setName] = useState('');
return (
setName(e.target.value)} placeholder="Enter name" className="w-full px-4 py-3 border rounded-lg" />
);
};
const Dashboard = ({ entries, formatDate, formatChartDate }) => {
const today = new Date().toISOString().split('T')[0];
const todayVitals = entries.vitals.filter(v => v.timestamp.startsWith(today));
const todayMental = entries.mental.filter(m => m.timestamp.startsWith(today));
const todayMeds = entries.medications.filter(m => m.timestamp.startsWith(today));
// Get daily data for last 14 days
const getDailyData = () => {
const dailyMap = new Map();
for (let i = 13; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
dailyMap.set(dateStr, {
date: formatChartDate(date.toISOString()),
vitalsCount: 0,
bpReadings: [],
bsReadings: [],
mentalEntries: 0,
moodAvg: null,
moods: [],
outburstCount: 0,
outburstDetails: [],
medsOnTime: 0,
medsLate: 0,
medsMissed: 0,
medsTotal: 0
});
}
entries.vitals.forEach(entry => {
const dateStr = entry.timestamp.split('T')[0];
if (dailyMap.has(dateStr)) {
const day = dailyMap.get(dateStr);
day.vitalsCount++;
if (entry.systolic && entry.diastolic) {
day.bpReadings.push({ systolic: entry.systolic, diastolic: entry.diastolic });
}
if (entry.bloodSugar) {
day.bsReadings.push(entry.bloodSugar);
}
}
});
entries.mental.forEach(entry => {
const dateStr = entry.timestamp.split('T')[0];
if (dailyMap.has(dateStr)) {
const day = dailyMap.get(dateStr);
day.mentalEntries++;
day.moods.push(entry.mood);
if (entry.hasOutburst) {
day.outburstCount++;
day.outburstDetails.push({
intensity: entry.intensity,
duration: entry.duration,
subjects: entry.subjects || []
});
}
}
});
// Calculate average mood
dailyMap.forEach((day) => {
if (day.moods.length > 0) {
day.moodAvg = Math.round((day.moods.reduce((a, b) => a + b, 0) / day.moods.length) * 10) / 10;
}
});
entries.medications.forEach(entry => {
const dateStr = entry.timestamp.split('T')[0];
if (dailyMap.has(dateStr)) {
const day = dailyMap.get(dateStr);
if (entry.morningMeds) {
day.medsTotal++;
if (entry.morningTakenAs === 'ontime') day.medsOnTime++;
else if (entry.morningTakenAs === 'late') day.medsLate++;
else if (entry.morningTakenAs === 'missed') day.medsMissed++;
}
if (entry.eveningMeds) {
day.medsTotal++;
if (entry.eveningTakenAs === 'ontime') day.medsOnTime++;
else if (entry.eveningTakenAs === 'late') day.medsLate++;
else if (entry.eveningTakenAs === 'missed') day.medsMissed++;
}
}
});
return Array.from(dailyMap.values());
};
const dailyData = getDailyData();
// Blood pressure data
const bpData = entries.vitals
.filter(v => v.systolic && v.diastolic)
.slice(0, 14)
.reverse()
.map(v => ({
date: formatChartDate(v.timestamp),
systolic: v.systolic,
diastolic: v.diastolic
}));
// Blood sugar data
const bsData = entries.vitals
.filter(v => v.bloodSugar)
.slice(0, 14)
.reverse()
.map(v => ({
date: formatChartDate(v.timestamp),
bloodSugar: v.bloodSugar
}));
// Mood data with outbursts
const moodData = entries.mental
.slice(0, 14)
.reverse()
.map(m => ({
date: formatChartDate(m.timestamp),
mood: m.mood,
hasOutburst: m.hasOutburst,
outburstInfo: m.hasOutburst ? `${m.intensity} - ${m.duration}${m.subjects && m.subjects.length ? '\nSubjects: ' + m.subjects.join(', ') : ''}` : null
}));
// Meds compliance data
const medsData = dailyData.filter(d => d.medsTotal > 0);
const CustomTooltip = ({ active, payload }) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
return (
{data.date}
{data.outburstInfo && (
Outburst Details:
{data.outburstInfo}
)}
{data.mood !== undefined && (
Mood: {data.mood}/10
)}
);
}
return null;
};
return (
Dashboard
{/* Today's Summary Cards */}
Today's Vitals
{todayVitals.length > 0 ? (
{todayVitals.map((v, idx) => (
{v.systolic &&
BP: {v.systolic}/{v.diastolic}
}
{v.bloodSugar &&
Sugar: {v.bloodSugar} mg/dL
}
{formatDate(v.timestamp)}
))}
) : (
No vitals recorded today
)}
Today's Mental State
{todayMental.length > 0 ? (
{todayMental.map((m, idx) => (
Mood: {m.mood}/10
{m.hasOutburst && (
⚠️ Outburst: {m.intensity}
)}
{formatDate(m.timestamp)}
))}
) : (
No mental state entries today
)}
{todayMeds.length > 0 ? (
{todayMeds.map((m, idx) => (
{m.morningMeds && (
Morning: {m.morningTakenAs}
)}
{m.eveningMeds && (
Evening: {m.eveningTakenAs}
)}
{formatDate(m.timestamp)}
))}
) : (
No medications recorded today
)}
{/* Daily Activity Summary Chart */}
Daily Activity Summary (Last 14 Days)
{/* Blood Pressure Chart */}
{bpData.length > 0 && (
Blood Pressure Trend (Last 14 Readings)
)}
{/* Blood Sugar Chart */}
{bsData.length > 0 && (
Blood Sugar Trend (Last 14 Readings)
)}
{/* Mood & Outbursts Visual */}
{moodData.length > 0 && (
Mood & Outbursts (Last 14 Entries)
{moodData.reverse().map((entry, idx) => (
{entry.date}
{/* Mood bar */}
= 7 ? 'bg-green-500' :
entry.mood >= 4 ? 'bg-yellow-500' :
'bg-red-500'
}`}
style={{ width: `${(entry.mood / 10) * 100}%` }}
>
{entry.mood}/10
{/* Outburst indicator */}
{entry.hasOutburst && (
⚠️ Outburst
)}
))}
⚠️
Hover for outburst details
)}
{/* Medication Compliance Visual */}
{medsData.length > 0 && (
Medication Compliance (Last 14 Days)
{medsData.reverse().map((day, idx) => (
{day.date}
{Array(day.medsOnTime).fill(0).map((_, i) => (
✓
))}
{Array(day.medsLate).fill(0).map((_, i) => (
⏰
))}
{Array(day.medsMissed).fill(0).map((_, i) => (
✗
))}
{day.medsTotal} total
))}
)}
);
};
const VitalsTab = ({ entries, onAdd, onEdit, onDelete, formatDate }) => {
return (
{entries.map(e => (
Vitals
{formatDate(e.timestamp)}
{e.systolic &&
BP: {e.systolic}/{e.diastolic}
}
{e.bloodSugar &&
Sugar: {e.bloodSugar}
}
))}
);
};
const MealsTab = ({ entries, onAdd, onEdit, onDelete, formatDate }) => {
return (
{entries.map(e => (
{e.mealType}
{formatDate(e.timestamp)}
{e.description &&
{e.description}
}
))}
);
};
const MentalTab = ({ entries, onAdd, onEdit, onDelete, formatDate }) => {
return (
{entries.map(e => (
Mental State
{formatDate(e.timestamp)}
Mood: {e.mood}/10
{e.hasOutburst &&
⚠️ Outburst: {e.intensity}
}
))}
);
};
const MedsTab = ({ entries, onAdd, onEdit, onDelete, formatDate }) => {
return (
{entries.map(e => (
Medications
{formatDate(e.timestamp)}
{e.morningMeds &&
Morning: {e.morningTakenAs}
}
{e.eveningMeds &&
Evening: {e.eveningTakenAs}
}
))}
);
};
const NotesTab = ({ entries, onAdd, onEdit, onDelete, formatDate }) => {
return (
{entries.map(e => (
{e.category}
{formatDate(e.timestamp)}
{e.note}
))}
);
};
const VitalForm = ({ entry, onSubmit, onCancel }) => {
const [data, setData] = useState({
date: entry ? new Date(entry.timestamp).toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
systolic: entry?.systolic || '',
diastolic: entry?.diastolic || '',
bloodSugar: entry?.bloodSugar || '',
heartRate: entry?.heartRate || '',
temperature: entry?.temperature || '',
oxygen: entry?.oxygen || '',
notes: entry?.notes || ''
});
const handleSubmit = () => {
const clean = {};
Object.keys(data).forEach(k => {
if (data[k]) clean[k] = k === 'notes' || k === 'date' ? data[k] : parseFloat(data[k]) || data[k];
});
if (entry) {
const ts = data.date ? new Date(data.date + 'T' + new Date(entry.timestamp).toTimeString().split(' ')[0]).toISOString() : entry.timestamp;
onSubmit({ ...entry, ...clean, timestamp: ts, date: undefined });
} else {
onSubmit(clean);
}
};
return (
);
};
const MealForm = ({ entry, onSubmit, onCancel }) => {
const [data, setData] = useState({
mealType: entry?.mealType || 'Breakfast',
description: entry?.description || '',
amountEaten: entry?.amountEaten || '',
fluids: entry?.fluids || '',
notes: entry?.notes || ''
});
return (
{entry ? 'Edit' : 'Add'} Meal
);
};
const MentalForm = ({ entry, onSubmit, onCancel }) => {
const subjects = ['Medicine', 'Dad leaving', 'Dad returning', 'Food/Meals', 'Bathing/Hygiene', 'Change of routine', 'Overstimulation', 'Pain/Discomfort', 'Confusion', 'Other'];
const [data, setData] = useState({
date: entry ? new Date(entry.timestamp).toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
mood: entry?.mood?.toString() || '5',
hasOutburst: entry?.hasOutburst || false,
subjects: entry?.subjects || [],
duration: entry?.duration || '<10 mins',
intensity: entry?.intensity || 'Low',
notes: entry?.notes || ''
});
const toggleSubject = (s) => {
setData(prev => ({
...prev,
subjects: prev.subjects.includes(s) ? prev.subjects.filter(x => x !== s) : [...prev.subjects, s]
}));
};
const handleSubmit = () => {
const sub = { mood: parseInt(data.mood), hasOutburst: data.hasOutburst, notes: data.notes };
if (data.hasOutburst) {
sub.subjects = data.subjects;
sub.duration = data.duration;
sub.intensity = data.intensity;
}
if (entry) {
const ts = data.date ? new Date(data.date + 'T' + new Date(entry.timestamp).toTimeString().split(' ')[0]).toISOString() : entry.timestamp;
onSubmit({ ...entry, ...sub, timestamp: ts, date: undefined });
} else {
if (data.date) sub.date = data.date;
onSubmit(sub);
}
};
return (
);
};
const MedForm = ({ entry, onSubmit, onCancel }) => {
const [data, setData] = useState({
date: entry ? new Date(entry.timestamp).toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
morningMeds: entry?.morningMeds || false,
morningTakenAs: entry?.morningTakenAs || 'ontime',
eveningMeds: entry?.eveningMeds || false,
eveningTakenAs: entry?.eveningTakenAs || 'ontime',
notes: entry?.notes || ''
});
const handleSubmit = () => {
if (entry) {
const ts = data.date ? new Date(data.date + 'T' + new Date(entry.timestamp).toTimeString().split(' ')[0]).toISOString() : entry.timestamp;
onSubmit({ ...entry, ...data, timestamp: ts, date: undefined });
} else {
onSubmit(data);
}
};
return (
);
};
const NoteForm = ({ entry, onSubmit, onCancel }) => {
const [data, setData] = useState({
category: entry?.category || 'General',
note: entry?.note || ''
});
return (
{entry ? 'Edit' : 'Add'} Note
);
};
export default ElderCareTracker;