/* @component-map * App — Main container, tab navigation [app.jsx] * SymptomLogger — Log new symptoms with type, severity, time, triggers, notes [components/SymptomLogger.jsx] * Timeline — Chronological timeline view of logged symptoms [components/Timeline.jsx] * Heatmap — Severity heatmap by time of day [components/Heatmap.jsx] * @end-component-map */ // DEPLOY_CONFIG: {"cron": [{"name": "weekly_health_insight_report", "schedule": "0 9 * * 1", "action": "event", "config": {"event_type": "analyze_weekly_symptom_patterns"}}], "triggers": [{"name": "send_weekly_health_insight_report_email", "on": "event.completed", "collection": "symptom_logs", "actions": [{"type": "email", "to": "{{config.recipient_emails}}", "subject": "Weekly Health Insight Report", "body": "Your weekly health insight report has been generated. It includes pattern analysis for symptom timing, likely triggers, and symptom clusters. Please review the attached/generated report in your app."}]}]} import { useEffect, useMemo, useState } from 'react'; import { useAuth, useCollection, renderChart } from '@deplixo/sdk'; import { SymptomLogger } from './components/SymptomLogger.jsx'; import { Timeline } from './components/Timeline.jsx'; import { Heatmap } from './components/Heatmap.jsx'; function ChartPanel({ title, subtitle, children }) { return (

{title}

{subtitle ?

{subtitle}

: null}
{children}
); } function FrequencyChart({ symptoms }) { const canvasRef = useState(null)[0]; const ref = useMemo(() => ({ current: null }), []); ref.current = canvasRef; const chartData = useMemo(() => { const counts = new Map(); symptoms.forEach(item => { const type = item?.type || 'Unknown'; counts.set(type, (counts.get(type) || 0) + 1); }); const labels = Array.from(counts.keys()); return { labels, datasets: [{ label: 'Symptom Count', data: labels.map(label => counts.get(label) || 0), }], }; }, [symptoms]); useEffect(() => { if (!ref.current || chartData.labels.length === 0) return; renderChart(ref.current, { type: 'bar', data: chartData, }); }, [chartData, ref]); return ; } function SeverityTrendChart({ symptoms }) { const canvasRef = useState(null)[0]; const ref = useMemo(() => ({ current: null }), []); ref.current = canvasRef; const chartData = useMemo(() => { const byDay = new Map(); symptoms.forEach(item => { const date = item?.time ? new Date(item.time) : null; if (!date || Number.isNaN(date.getTime())) return; const key = date.toISOString().slice(0, 10); const prev = byDay.get(key) || { sum: 0, count: 0 }; prev.sum += Number(item?.severity || 0); prev.count += 1; byDay.set(key, prev); }); const labels = Array.from(byDay.keys()).sort(); return { labels, datasets: [{ label: 'Average Severity', data: labels.map(label => { const v = byDay.get(label); return v && v.count ? Number((v.sum / v.count).toFixed(2)) : 0; }), }], }; }, [symptoms]); useEffect(() => { if (!ref.current || chartData.labels.length === 0) return; renderChart(ref.current, { type: 'line', data: chartData, }); }, [chartData, ref]); return ; } function TriggerCorrelationChart({ symptoms }) { const canvasRef = useState(null)[0]; const ref = useMemo(() => ({ current: null }), []); ref.current = canvasRef; const chartData = useMemo(() => { const triggerTotals = new Map(); symptoms.forEach(item => { const severity = Number(item?.severity || 0); const triggers = Array.isArray(item?.triggers) ? item.triggers : []; triggers.forEach(trigger => { const key = String(trigger || 'Unknown'); const prev = triggerTotals.get(key) || { sum: 0, count: 0 }; prev.sum += severity; prev.count += 1; triggerTotals.set(key, prev); }); }); const entries = Array.from(triggerTotals.entries()) .map(([label, value]) => ({ label, avg: value.count ? value.sum / value.count : 0, count: value.count })) .sort((a, b) => b.avg - a.avg) .slice(0, 10); return { labels: entries.map(entry => entry.label), datasets: [{ label: 'Avg Severity When Trigger Present', data: entries.map(entry => Number(entry.avg.toFixed(2))), }], }; }, [symptoms]); useEffect(() => { if (!ref.current || chartData.labels.length === 0) return; renderChart(ref.current, { type: 'bar', data: chartData, }); }, [chartData, ref]); return ; } function App() { const { user, loading, login, logout } = useAuth(); const { items: allSymptoms, loading: symptomsLoading } = useCollection('symptoms', { personal: true }); const [activeTab, setActiveTab] = useState('timeline'); const [searchTerm, setSearchTerm] = useState(''); const [selectedType, setSelectedType] = useState('all'); const [dateRange, setDateRange] = useState('all'); const symptomItems = useMemo(() => Array.isArray(allSymptoms) ? allSymptoms : [], [allSymptoms]); const symptomTypes = useMemo(() => { const types = new Set(); symptomItems.forEach(item => { if (item?.type) types.add(item.type); }); return ['all', ...Array.from(types).sort()]; }, [symptomItems]); const filteredSymptoms = useMemo(() => { const term = searchTerm.trim().toLowerCase(); const now = new Date(); return symptomItems.filter(item => { const itemDate = item?.time ? new Date(item.time) : null; const matchesSearch = !term || [item?.type, item?.notes, ...(Array.isArray(item?.triggers) ? item.triggers : [])] .filter(Boolean) .some(value => String(value).toLowerCase().includes(term)); const matchesType = selectedType === 'all' || item?.type === selectedType; let matchesDate = true; if (dateRange !== 'all' && itemDate && !Number.isNaN(itemDate.getTime())) { const diffMs = now.getTime() - itemDate.getTime(); const diffDays = diffMs / (1000 * 60 * 60 * 24); if (dateRange === 'today') matchesDate = diffDays < 1; if (dateRange === '7d') matchesDate = diffDays < 7; if (dateRange === '30d') matchesDate = diffDays < 30; } else if (dateRange !== 'all' && !itemDate) { matchesDate = false; } return matchesSearch && matchesType && matchesDate; }); }, [symptomItems, searchTerm, selectedType, dateRange]); useEffect(() => { if (activeTab !== 'log') return; if (selectedType === 'all') return; const currentTypes = symptomTypes.slice(1); if (!currentTypes.includes(selectedType)) setSelectedType('all'); }, [activeTab, selectedType, symptomTypes]); const tabs = [ { id: 'timeline', label: '📋 Timeline', icon: '📋' }, { id: 'log', label: '+ Log', icon: '+' }, { id: 'heatmap', label: '🔥 Heatmap', icon: '🔥' }, ]; const sharedViewProps = { user, symptoms: filteredSymptoms, allSymptoms: symptomItems }; if (loading) { return
Signing in...
; } if (!user) { return (

Symptom Tracker

Sign in with Google to access your private symptom data

); } const chartSymptoms = filteredSymptoms; return (

Symptom Tracker

Monitor your chronic condition patterns

{user.name}

Search & Filters

{filteredSymptoms.length} result{filteredSymptoms.length === 1 ? '' : 's'}
{activeTab === 'timeline' && } {activeTab === 'log' && setActiveTab('timeline')} defaultType={selectedType !== 'all' ? selectedType : ''} />} {activeTab === 'heatmap' && }
); } ReactDOM.createRoot(document.getElementById("root")).render();