build: eb8f08f0-e41a-4086-9130-c5af05b58ec7
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonRange,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
IonAlert,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
chevronBackOutline,
|
||||
trashOutline,
|
||||
medkitOutline,
|
||||
} from 'ionicons/icons';
|
||||
import {
|
||||
getCheckups,
|
||||
addCheckup,
|
||||
deleteCheckup,
|
||||
clearCheckups,
|
||||
} from '../services/localHealthStore';
|
||||
import { CheckupEntry, CheckupMood } from '../types/health';
|
||||
import HealthLogList from '../components/health/HealthLogList';
|
||||
|
||||
const Checkups: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const [entries, setEntries] = useState<CheckupEntry[]>([]);
|
||||
const [mood, setMood] = useState<CheckupMood | ''>('');
|
||||
const [energy, setEnergy] = useState<number>(70);
|
||||
const [symptoms, setSymptoms] = useState<string>('');
|
||||
const [note, setNote] = useState<string>('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showClearAlert, setShowClearAlert] = useState(false);
|
||||
|
||||
const refreshData = () => {
|
||||
setEntries(getCheckups());
|
||||
};
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
if (!mood) {
|
||||
setError('Please select a mood');
|
||||
setTimeout(() => setError(null), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
addCheckup({
|
||||
mood: mood as CheckupMood,
|
||||
energy,
|
||||
symptoms,
|
||||
note,
|
||||
});
|
||||
|
||||
setMood('');
|
||||
setEnergy(70);
|
||||
setSymptoms('');
|
||||
setNote('');
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
deleteCheckup(id);
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
clearCheckups();
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const moods: CheckupMood[] = ['Great', 'Okay', 'Low', 'Unwell'];
|
||||
|
||||
return (
|
||||
<IonPage style={{ background: '#eef7fb' }}>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar style={{ '--background': '#eef7fb', '--color': '#1e3a5f' }}>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => history.goBack()}>
|
||||
<IonIcon icon={chevronBackOutline} slot="icon-only" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle style={{ fontWeight: '700' }}>Self Checkup</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
{entries.length > 0 && (
|
||||
<IonButton onClick={() => setShowClearAlert(true)}>
|
||||
<IonIcon
|
||||
icon={trashOutline}
|
||||
slot="icon-only"
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
</IonButton>
|
||||
)}
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent
|
||||
style={{
|
||||
'--background': '#eef7fb',
|
||||
'--padding-start': '20px',
|
||||
'--padding-end': '20px',
|
||||
'--padding-top': '16px',
|
||||
}}
|
||||
>
|
||||
{/* Form Card */}
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
borderRadius: '24px',
|
||||
padding: '24px',
|
||||
marginBottom: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ fontSize: '18px', fontWeight: '700', color: '#1e3a5f' }}
|
||||
>
|
||||
How do you feel right now?
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '12px',
|
||||
}}
|
||||
>
|
||||
Mood
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
{moods.map((m) => (
|
||||
<div
|
||||
key={m}
|
||||
onClick={() => setMood(m)}
|
||||
style={{
|
||||
padding: '10px 16px',
|
||||
borderRadius: '999px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
cursor: 'pointer',
|
||||
background: mood === m ? '#0369a1' : '#e2f2f8',
|
||||
color: mood === m ? '#ffffff' : '#1e3a5f',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
>
|
||||
{m}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
}}
|
||||
>
|
||||
Energy
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: '#0369a1',
|
||||
}}
|
||||
>
|
||||
{energy}%
|
||||
</span>
|
||||
</div>
|
||||
<IonRange
|
||||
value={energy}
|
||||
onIonChange={(e) => setEnergy(e.detail.value as number)}
|
||||
style={{
|
||||
'--bar-background': '#e2f2f8',
|
||||
'--bar-background-active': '#38bdf8',
|
||||
'--knob-background': '#0369a1',
|
||||
padding: '0',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Symptoms
|
||||
</div>
|
||||
<IonInput
|
||||
value={symptoms}
|
||||
onIonInput={(e) => setSymptoms(e.detail.value!)}
|
||||
placeholder="e.g. Headache, fatigue..."
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--padding-end': '16px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Notes
|
||||
</div>
|
||||
<IonTextarea
|
||||
value={note}
|
||||
onIonInput={(e) => setNote(e.detail.value!)}
|
||||
placeholder="Add more details..."
|
||||
rows={3}
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--padding-end': '16px',
|
||||
'--padding-top': '12px',
|
||||
'--padding-bottom': '12px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: '#dc2626',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={handleSave}
|
||||
style={{
|
||||
'--background': '#0369a1',
|
||||
'--border-radius': '999px',
|
||||
'--color': '#ffffff',
|
||||
fontWeight: '700',
|
||||
height: '48px',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
Save checkup
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
{/* History Section */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
margin: '0 0 12px',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{ fontSize: '17px', fontWeight: '700', color: '#1e3a5f' }}
|
||||
>
|
||||
History
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
color: 'rgba(30,58,95,0.40)',
|
||||
}}
|
||||
>
|
||||
{entries.length} logs
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<HealthLogList
|
||||
type="checkup"
|
||||
entries={entries}
|
||||
onDelete={handleDelete}
|
||||
emptyTitle="No checkups yet"
|
||||
emptyActionLabel="Save your first checkup"
|
||||
onEmptyAction={() => {}} // Form is already on page
|
||||
/>
|
||||
|
||||
<div style={{ height: '40px' }} />
|
||||
|
||||
<IonAlert
|
||||
isOpen={showClearAlert}
|
||||
onDidDismiss={() => setShowClearAlert(false)}
|
||||
header="Clear history?"
|
||||
message="This will delete all checkup logs permanently."
|
||||
buttons={[
|
||||
{ text: 'Cancel', role: 'cancel' },
|
||||
{ text: 'Clear All', role: 'destructive', handler: handleClear },
|
||||
]}
|
||||
/>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Checkups;
|
||||
@@ -0,0 +1,376 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonAlert,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
chevronBackOutline,
|
||||
trashOutline,
|
||||
waterOutline,
|
||||
leafOutline,
|
||||
} from 'ionicons/icons';
|
||||
import {
|
||||
getDietEntries,
|
||||
addDietEntry,
|
||||
deleteDietEntry,
|
||||
clearDietEntries,
|
||||
getWellbeingSummary,
|
||||
} from '../services/localHealthStore';
|
||||
import { DietEntry, MealType, WellbeingSummary } from '../types/health';
|
||||
import HealthLogList from '../components/health/HealthLogList';
|
||||
import MetricMiniCard from '../components/health/MetricMiniCard';
|
||||
|
||||
const Diet: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const [entries, setEntries] = useState<DietEntry[]>([]);
|
||||
const [summary, setSummary] = useState<WellbeingSummary | null>(null);
|
||||
|
||||
const [mealType, setMealType] = useState<MealType | ''>('');
|
||||
const [waterCups, setWaterCups] = useState<string>('');
|
||||
const [produceServings, setProduceServings] = useState<string>('');
|
||||
const [notes, setNotes] = useState<string>('');
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showClearAlert, setShowClearAlert] = useState(false);
|
||||
|
||||
const refreshData = () => {
|
||||
setEntries(getDietEntries());
|
||||
setSummary(getWellbeingSummary());
|
||||
};
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
if (!mealType) {
|
||||
setError('Please select a meal type');
|
||||
setTimeout(() => setError(null), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
addDietEntry({
|
||||
mealType: mealType as MealType,
|
||||
waterCups: parseInt(waterCups) || 0,
|
||||
produceServings: parseInt(produceServings) || 0,
|
||||
notes,
|
||||
});
|
||||
|
||||
setMealType('');
|
||||
setWaterCups('');
|
||||
setProduceServings('');
|
||||
setNotes('');
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
deleteDietEntry(id);
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
clearDietEntries();
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const mealTypes: MealType[] = ['Breakfast', 'Lunch', 'Dinner', 'Snack'];
|
||||
|
||||
return (
|
||||
<IonPage style={{ background: '#eef7fb' }}>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar style={{ '--background': '#eef7fb', '--color': '#1e3a5f' }}>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => history.goBack()}>
|
||||
<IonIcon icon={chevronBackOutline} slot="icon-only" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle style={{ fontWeight: '700' }}>Diet</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
{entries.length > 0 && (
|
||||
<IonButton onClick={() => setShowClearAlert(true)}>
|
||||
<IonIcon
|
||||
icon={trashOutline}
|
||||
slot="icon-only"
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
</IonButton>
|
||||
)}
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent
|
||||
style={{
|
||||
'--background': '#eef7fb',
|
||||
'--padding-start': '20px',
|
||||
'--padding-end': '20px',
|
||||
'--padding-top': '16px',
|
||||
}}
|
||||
>
|
||||
{/* Form Card */}
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
borderRadius: '24px',
|
||||
padding: '24px',
|
||||
marginBottom: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ fontSize: '18px', fontWeight: '700', color: '#1e3a5f' }}
|
||||
>
|
||||
Log today’s nutrition
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Meal Type
|
||||
</div>
|
||||
<IonSelect
|
||||
value={mealType}
|
||||
onIonChange={(e) => setMealType(e.detail.value)}
|
||||
interface="action-sheet"
|
||||
placeholder="Select a meal"
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--padding-end': '16px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
width: '100%',
|
||||
'--placeholder-color': 'rgba(30,58,95,0.38)',
|
||||
}}
|
||||
>
|
||||
{mealTypes.map((m) => (
|
||||
<IonSelectOption key={m} value={m}>
|
||||
{m}
|
||||
</IonSelectOption>
|
||||
))}
|
||||
</IonSelect>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '12px',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Water (cups)
|
||||
</div>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
value={waterCups}
|
||||
onIonInput={(e) =>
|
||||
setWaterCups(e.detail.value!.replace(/[^0-9]/g, ''))
|
||||
}
|
||||
placeholder="0"
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Produce (servings)
|
||||
</div>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
value={produceServings}
|
||||
onIonInput={(e) =>
|
||||
setProduceServings(e.detail.value!.replace(/[^0-9]/g, ''))
|
||||
}
|
||||
placeholder="0"
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Notes
|
||||
</div>
|
||||
<IonTextarea
|
||||
value={notes}
|
||||
onIonInput={(e) => setNotes(e.detail.value!)}
|
||||
placeholder="Add more details..."
|
||||
rows={3}
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--padding-end': '16px',
|
||||
'--padding-top': '12px',
|
||||
'--padding-bottom': '12px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: '#dc2626',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={handleSave}
|
||||
style={{
|
||||
'--background': '#0369a1',
|
||||
'--border-radius': '999px',
|
||||
'--color': '#ffffff',
|
||||
fontWeight: '700',
|
||||
height: '48px',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
Save diet log
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
{/* Today Summary */}
|
||||
{summary && (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '12px',
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
>
|
||||
<MetricMiniCard
|
||||
label="Today's Water"
|
||||
value={summary.hydrationCups.toString()}
|
||||
helper="cups"
|
||||
progress={summary.hydrationCups / 8}
|
||||
icon={waterOutline}
|
||||
/>
|
||||
<MetricMiniCard
|
||||
label="Today's Produce"
|
||||
value={summary.produceServings.toString()}
|
||||
helper="servings"
|
||||
progress={summary.produceServings / 5}
|
||||
icon={leafOutline}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* History Section */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
margin: '0 0 12px',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{ fontSize: '17px', fontWeight: '700', color: '#1e3a5f' }}
|
||||
>
|
||||
History
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
color: 'rgba(30,58,95,0.40)',
|
||||
}}
|
||||
>
|
||||
{entries.length} logs
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<HealthLogList
|
||||
type="diet"
|
||||
entries={entries}
|
||||
onDelete={handleDelete}
|
||||
emptyTitle="No diet logs yet"
|
||||
emptyActionLabel="Log a meal"
|
||||
onEmptyAction={() => {}} // Form is already on page
|
||||
/>
|
||||
|
||||
<div style={{ height: '40px' }} />
|
||||
|
||||
<IonAlert
|
||||
isOpen={showClearAlert}
|
||||
onDidDismiss={() => setShowClearAlert(false)}
|
||||
header="Clear history?"
|
||||
message="This will delete all diet logs permanently."
|
||||
buttons={[
|
||||
{ text: 'Cancel', role: 'cancel' },
|
||||
{ text: 'Clear All', role: 'destructive', handler: handleClear },
|
||||
]}
|
||||
/>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Diet;
|
||||
@@ -0,0 +1,242 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonIcon,
|
||||
IonText,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
heartCircleOutline,
|
||||
medkitOutline,
|
||||
restaurantOutline,
|
||||
moonOutline,
|
||||
chevronForwardOutline,
|
||||
} from 'ionicons/icons';
|
||||
import WellbeingScoreCard from '../components/health/WellbeingScoreCard';
|
||||
import MetricMiniCard from '../components/health/MetricMiniCard';
|
||||
import RecentActivityList from '../components/health/RecentActivityList';
|
||||
import {
|
||||
getWellbeingSummary,
|
||||
getRecentActivity,
|
||||
} from '../services/localHealthStore';
|
||||
import { WellbeingSummary, ActivityEntry } from '../types/health';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const [summary, setSummary] = useState<WellbeingSummary>(
|
||||
getWellbeingSummary()
|
||||
);
|
||||
const [recentEntries, setRecentEntries] =
|
||||
useState<ActivityEntry[]>(getRecentActivity());
|
||||
|
||||
const refreshData = () => {
|
||||
setSummary(getWellbeingSummary());
|
||||
setRecentEntries(getRecentActivity());
|
||||
};
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
return (
|
||||
<IonPage style={{ background: '#eef7fb' }}>
|
||||
<IonContent
|
||||
fullscreen
|
||||
style={{
|
||||
'--background':
|
||||
'linear-gradient(160deg, rgba(56,189,248,0.16) 0%, #eef7fb 58%, #f8fbfd 100%)',
|
||||
}}
|
||||
>
|
||||
{/* Top Content Row */}
|
||||
<div
|
||||
style={{
|
||||
padding: 'calc(16px + var(--ion-safe-area-top)) 20px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
background: 'rgba(3,105,161,0.12)',
|
||||
borderRadius: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<IonIcon
|
||||
icon={heartCircleOutline}
|
||||
style={{ fontSize: '22px', color: '#0369a1' }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
lineHeight: '16px',
|
||||
}}
|
||||
>
|
||||
Wellbeing
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '20px',
|
||||
fontWeight: '700',
|
||||
color: '#1e3a5f',
|
||||
lineHeight: '24px',
|
||||
}}
|
||||
>
|
||||
Good morning
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
borderRadius: '999px',
|
||||
padding: '8px 12px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '700',
|
||||
color: '#0369a1',
|
||||
}}
|
||||
>
|
||||
Local
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Score Widget */}
|
||||
<WellbeingScoreCard
|
||||
score={summary.score}
|
||||
message={summary.scoreMessage}
|
||||
progress={summary.scoreProgress}
|
||||
/>
|
||||
|
||||
{/* Metric Row */}
|
||||
<div
|
||||
style={{
|
||||
margin: '0 20px 12px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '12px',
|
||||
}}
|
||||
>
|
||||
<MetricMiniCard
|
||||
label="Sleep"
|
||||
value={summary.sleepHours.toString()}
|
||||
helper="hrs"
|
||||
progress={summary.sleepHours / 8}
|
||||
icon={moonOutline}
|
||||
/>
|
||||
<MetricMiniCard
|
||||
label="Water"
|
||||
value={summary.hydrationCups.toString()}
|
||||
helper="cups"
|
||||
progress={summary.hydrationCups / 8}
|
||||
icon={restaurantOutline}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Action Strip */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
overflowX: 'auto',
|
||||
padding: '0 20px 20px',
|
||||
msOverflowStyle: 'none',
|
||||
scrollbarWidth: 'none',
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{ label: 'Checkup', icon: medkitOutline, path: '/checkups' },
|
||||
{ label: 'Diet', icon: restaurantOutline, path: '/diet' },
|
||||
{ label: 'Sleep', icon: moonOutline, path: '/sleep' },
|
||||
].map((action) => (
|
||||
<div
|
||||
key={action.label}
|
||||
onClick={() => history.push(action.path)}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
background: '#ffffff',
|
||||
borderRadius: '999px',
|
||||
padding: '10px 18px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
minHeight: '44px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<IonIcon
|
||||
icon={action.icon}
|
||||
style={{ fontSize: '18px', color: '#0369a1' }}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
>
|
||||
{action.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Section Header */}
|
||||
<div
|
||||
style={{
|
||||
margin: '4px 20px 12px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{ fontSize: '17px', fontWeight: '700', color: '#1e3a5f' }}
|
||||
>
|
||||
Recent Activity
|
||||
</span>
|
||||
<div
|
||||
onClick={() => history.push('/insights')}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '2px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{ fontSize: '13px', fontWeight: '700', color: '#0369a1' }}
|
||||
>
|
||||
See insights
|
||||
</span>
|
||||
<IonIcon
|
||||
icon={chevronForwardOutline}
|
||||
style={{ fontSize: '14px', color: '#0369a1' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity List */}
|
||||
<RecentActivityList
|
||||
entries={recentEntries}
|
||||
onCreate={() => history.push('/checkups')}
|
||||
onSeeInsights={() => history.push('/insights')}
|
||||
/>
|
||||
|
||||
<div style={{ height: '20px' }} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,204 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonGrid,
|
||||
IonRow,
|
||||
IonCol,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
chevronBackOutline,
|
||||
analyticsOutline,
|
||||
medkitOutline,
|
||||
restaurantOutline,
|
||||
moonOutline,
|
||||
} from 'ionicons/icons';
|
||||
import {
|
||||
getWellbeingSummary,
|
||||
calculateInsights,
|
||||
} from '../services/localHealthStore';
|
||||
import { WellbeingSummary, LocalInsight } from '../types/health';
|
||||
import {
|
||||
InsightSummaryCard,
|
||||
RecommendationList,
|
||||
} from '../components/health/InsightCards';
|
||||
|
||||
const Insights: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const [summary, setSummary] = useState<WellbeingSummary>(
|
||||
getWellbeingSummary()
|
||||
);
|
||||
const [insights, setInsights] = useState<LocalInsight[]>(calculateInsights());
|
||||
|
||||
const refreshData = () => {
|
||||
setSummary(getWellbeingSummary());
|
||||
setInsights(calculateInsights());
|
||||
};
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
return (
|
||||
<IonPage style={{ background: '#eef7fb' }}>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar style={{ '--background': '#eef7fb', '--color': '#1e3a5f' }}>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => history.goBack()}>
|
||||
<IonIcon icon={chevronBackOutline} slot="icon-only" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle style={{ fontWeight: '700' }}>Insights</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent
|
||||
style={{
|
||||
'--background': '#eef7fb',
|
||||
'--padding-start': '20px',
|
||||
'--padding-end': '20px',
|
||||
'--padding-top': '16px',
|
||||
}}
|
||||
>
|
||||
{insights.length > 0 ? (
|
||||
<>
|
||||
<InsightSummaryCard
|
||||
summary={summary}
|
||||
primaryInsight={insights[0]}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
margin: '24px 0 12px',
|
||||
fontSize: '17px',
|
||||
fontWeight: '700',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
>
|
||||
Habit Guidance
|
||||
</div>
|
||||
|
||||
<RecommendationList insights={insights} />
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
borderRadius: '24px',
|
||||
padding: '40px 24px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
marginTop: '20px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
borderRadius: '20px',
|
||||
background: 'rgba(3,105,161,0.06)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
>
|
||||
<IonIcon
|
||||
icon={analyticsOutline}
|
||||
style={{ fontSize: '32px', color: 'rgba(3,105,161,0.30)' }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
fontWeight: '700',
|
||||
color: '#1e3a5f',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Insights need a few logs first
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
color: 'rgba(30,58,95,0.58)',
|
||||
lineHeight: '20px',
|
||||
marginBottom: '32px',
|
||||
}}
|
||||
>
|
||||
Add a checkup, meal, or sleep log to see personalized guidance
|
||||
based on your patterns.
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '12px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<IonButton
|
||||
onClick={() => history.push('/checkups')}
|
||||
style={{
|
||||
'--background': '#0369a1',
|
||||
'--border-radius': '999px',
|
||||
'--color': '#ffffff',
|
||||
fontWeight: '700',
|
||||
height: '48px',
|
||||
}}
|
||||
>
|
||||
<IonIcon icon={medkitOutline} slot="start" />
|
||||
Add Checkup
|
||||
</IonButton>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
<IonButton
|
||||
onClick={() => history.push('/diet')}
|
||||
style={{
|
||||
flex: 1,
|
||||
'--background': '#e2f2f8',
|
||||
'--border-radius': '999px',
|
||||
'--color': '#1e3a5f',
|
||||
fontWeight: '700',
|
||||
height: '48px',
|
||||
}}
|
||||
>
|
||||
<IonIcon icon={restaurantOutline} slot="start" />
|
||||
Diet
|
||||
</IonButton>
|
||||
<IonButton
|
||||
onClick={() => history.push('/sleep')}
|
||||
style={{
|
||||
flex: 1,
|
||||
'--background': '#e2f2f8',
|
||||
'--border-radius': '999px',
|
||||
'--color': '#1e3a5f',
|
||||
fontWeight: '700',
|
||||
height: '48px',
|
||||
}}
|
||||
>
|
||||
<IonIcon icon={moonOutline} slot="start" />
|
||||
Sleep
|
||||
</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ height: '40px' }} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Insights;
|
||||
@@ -0,0 +1,351 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
IonAlert,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { chevronBackOutline, trashOutline, moonOutline } from 'ionicons/icons';
|
||||
import {
|
||||
getSleepEntries,
|
||||
addSleepEntry,
|
||||
deleteSleepEntry,
|
||||
clearSleepEntries,
|
||||
} from '../services/localHealthStore';
|
||||
import { SleepEntry, SleepQuality } from '../types/health';
|
||||
import HealthLogList from '../components/health/HealthLogList';
|
||||
import SleepTrendCard from '../components/health/SleepTrendCard';
|
||||
|
||||
const Sleep: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const [entries, setEntries] = useState<SleepEntry[]>([]);
|
||||
|
||||
const [hours, setHours] = useState<string>('');
|
||||
const [quality, setQuality] = useState<SleepQuality | ''>('');
|
||||
const [interruptions, setInterruptions] = useState<string>('');
|
||||
const [notes, setNotes] = useState<string>('');
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showClearAlert, setShowClearAlert] = useState(false);
|
||||
|
||||
const refreshData = () => {
|
||||
setEntries(getSleepEntries());
|
||||
};
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
const hoursNum = parseFloat(hours);
|
||||
if (!hours || isNaN(hoursNum) || hoursNum <= 0) {
|
||||
setError('Please enter a valid sleep duration');
|
||||
setTimeout(() => setError(null), 4000);
|
||||
return;
|
||||
}
|
||||
if (!quality) {
|
||||
setError('Please select sleep quality');
|
||||
setTimeout(() => setError(null), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
addSleepEntry({
|
||||
hours: hoursNum,
|
||||
quality: quality as SleepQuality,
|
||||
interruptions: parseInt(interruptions) || 0,
|
||||
notes,
|
||||
});
|
||||
|
||||
setHours('');
|
||||
setQuality('');
|
||||
setInterruptions('');
|
||||
setNotes('');
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
deleteSleepEntry(id);
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
clearSleepEntries();
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const qualities: SleepQuality[] = ['Restful', 'Okay', 'Restless'];
|
||||
|
||||
return (
|
||||
<IonPage style={{ background: '#eef7fb' }}>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar style={{ '--background': '#eef7fb', '--color': '#1e3a5f' }}>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => history.goBack()}>
|
||||
<IonIcon icon={chevronBackOutline} slot="icon-only" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle style={{ fontWeight: '700' }}>Sleep</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
{entries.length > 0 && (
|
||||
<IonButton onClick={() => setShowClearAlert(true)}>
|
||||
<IonIcon
|
||||
icon={trashOutline}
|
||||
slot="icon-only"
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
</IonButton>
|
||||
)}
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent
|
||||
style={{
|
||||
'--background': '#eef7fb',
|
||||
'--padding-start': '20px',
|
||||
'--padding-end': '20px',
|
||||
'--padding-top': '16px',
|
||||
}}
|
||||
>
|
||||
{/* Form Card */}
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
borderRadius: '24px',
|
||||
padding: '24px',
|
||||
marginBottom: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ fontSize: '18px', fontWeight: '700', color: '#1e3a5f' }}
|
||||
>
|
||||
Record your sleep
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '12px',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Duration (hours)
|
||||
</div>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={hours}
|
||||
onIonInput={(e) => {
|
||||
const val = e.detail.value!.replace(/[^0-9.]/g, '');
|
||||
if ((val.match(/\./g) || []).length <= 1) setHours(val);
|
||||
}}
|
||||
placeholder="0.0"
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Interruptions
|
||||
</div>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
value={interruptions}
|
||||
onIonInput={(e) =>
|
||||
setInterruptions(e.detail.value!.replace(/[^0-9]/g, ''))
|
||||
}
|
||||
placeholder="0"
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '12px',
|
||||
}}
|
||||
>
|
||||
Quality
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
{qualities.map((q) => (
|
||||
<div
|
||||
key={q}
|
||||
onClick={() => setQuality(q)}
|
||||
style={{
|
||||
padding: '10px 16px',
|
||||
borderRadius: '999px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
cursor: 'pointer',
|
||||
background: quality === q ? '#0369a1' : '#e2f2f8',
|
||||
color: quality === q ? '#ffffff' : '#1e3a5f',
|
||||
transition: 'all 0.2s ease',
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{q}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '700',
|
||||
color: 'rgba(30,58,95,0.60)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Notes
|
||||
</div>
|
||||
<IonTextarea
|
||||
value={notes}
|
||||
onIonInput={(e) => setNotes(e.detail.value!)}
|
||||
placeholder="Add more details..."
|
||||
rows={3}
|
||||
style={{
|
||||
'--background': '#f3f8fb',
|
||||
'--padding-start': '16px',
|
||||
'--padding-end': '16px',
|
||||
'--padding-top': '12px',
|
||||
'--padding-bottom': '12px',
|
||||
'--border-radius': '12px',
|
||||
fontSize: '14px',
|
||||
color: '#1e3a5f',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: '#dc2626',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={handleSave}
|
||||
style={{
|
||||
'--background': '#0369a1',
|
||||
'--border-radius': '999px',
|
||||
'--color': '#ffffff',
|
||||
fontWeight: '700',
|
||||
height: '48px',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
Save sleep log
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
{/* Trend Widget */}
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<SleepTrendCard entries={entries} />
|
||||
</div>
|
||||
|
||||
{/* History Section */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
margin: '0 0 12px',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{ fontSize: '17px', fontWeight: '700', color: '#1e3a5f' }}
|
||||
>
|
||||
History
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
color: 'rgba(30,58,95,0.40)',
|
||||
}}
|
||||
>
|
||||
{entries.length} logs
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<HealthLogList
|
||||
type="sleep"
|
||||
entries={entries}
|
||||
onDelete={handleDelete}
|
||||
emptyTitle="No sleep logs yet"
|
||||
emptyActionLabel="Log sleep"
|
||||
onEmptyAction={() => {}} // Form is already on page
|
||||
/>
|
||||
|
||||
<div style={{ height: '40px' }} />
|
||||
|
||||
<IonAlert
|
||||
isOpen={showClearAlert}
|
||||
onDidDismiss={() => setShowClearAlert(false)}
|
||||
header="Clear history?"
|
||||
message="This will delete all sleep logs permanently."
|
||||
buttons={[
|
||||
{ text: 'Cancel', role: 'cancel' },
|
||||
{ text: 'Clear All', role: 'destructive', handler: handleClear },
|
||||
]}
|
||||
/>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sleep;
|
||||
Reference in New Issue
Block a user