Files
appcakes-builds/src/pages/Stats.tsx
T
2026-05-28 14:21:39 +00:00

175 lines
4.8 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react';
import {
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonPage,
IonSegment,
IonSegmentButton,
IonTitle,
IonToolbar,
} from '@ionic/react';
import { IonBackButton } from '@ionic/react';
import { calendarOutline } from 'ionicons/icons';
import { Redirect } from 'react-router-dom';
import type { Tables } from '../database.types';
import StatsHabitCard from '../components/StatsHabitCard';
import { supabase } from '../supabase';
import {
Style,
setStatusBarBackground,
setStatusBarStyle,
} from '../utils/statusBar';
import { useIonViewWillEnter } from '@ionic/react';
interface HabitWithMeta extends Tables<'habits'> {
completions: Tables<'habit_completions'>[];
}
const formatDate = (date: Date) => date.toISOString().slice(0, 10);
const calculateCurrentStreak = (dates: string[]) => {
const unique = Array.from(new Set(dates)).sort((a, b) => b.localeCompare(a));
let streak = 0;
let cursor = new Date();
if (!unique.includes(formatDate(cursor))) {
cursor.setDate(cursor.getDate() - 1);
}
while (unique.includes(formatDate(cursor))) {
streak += 1;
cursor.setDate(cursor.getDate() - 1);
}
return streak;
};
const Stats: React.FC = () => {
const [period, setPeriod] = useState<'7' | '30'>('7');
const [sessionChecked, setSessionChecked] = useState(false);
const [userId, setUserId] = useState<string | null>(null);
const [habits, setHabits] = useState<HabitWithMeta[]>([]);
useEffect(() => {
const load = async () => {
const {
data: { session },
} = await supabase.auth.getSession();
if (!session?.user) {
setSessionChecked(true);
setUserId(null);
return;
}
setUserId(session.user.id);
setSessionChecked(true);
const { data } = await supabase
.from('habits')
.select('*, completions:habit_completions(*)')
.order('created_at', { ascending: true });
setHabits((data ?? []) as HabitWithMeta[]);
};
load();
}, []);
const days = Number(period);
const visibleDates = useMemo(() => {
return Array.from({ length: days }, (_, index) => {
const date = new Date();
date.setDate(date.getDate() - index);
return formatDate(date);
});
}, [days]);
const summary = useMemo(() => {
return habits.map((habit) => {
const completionDates = habit.completions.map(
(entry) => entry.completed_on,
);
const completedCount = completionDates.filter((date) =>
visibleDates.includes(date),
).length;
return {
id: habit.id,
name: habit.name,
color: habit.color,
completedCount,
streak: calculateCurrentStreak(completionDates),
};
});
}, [habits, visibleDates]);
if (sessionChecked && !userId) {
return <Redirect to="/auth" />;
}
return (
<IonPage>
<IonHeader className="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/home" />
</IonButtons>
<IonTitle>Stats</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<div className="dashboard-shell ion-padding">
<section className="hero-card compact">
<span className="eyebrow">Consistency view</span>
<h1>See which habits you&apos;re actually keeping.</h1>
<p>
Flip between the last week and month to spot patterns before a
streak disappears.
</p>
</section>
<div className="stats-filter-row">
<IonIcon icon={calendarOutline} />
<IonSegment
value={period}
onIonChange={(e) => setPeriod(e.detail.value as '7' | '30')}
>
<IonSegmentButton value="7">Last 7 days</IonSegmentButton>
<IonSegmentButton value="30">Last 30 days</IonSegmentButton>
</IonSegment>
</div>
{summary.length === 0 ? (
<div className="inline-message">
<h3>No stats yet</h3>
<p>
Create a habit and start checking it off daily to unlock
consistency insights.
</p>
<IonButton routerLink="/home">Go to dashboard</IonButton>
</div>
) : (
<div className="stack-list">
{summary.map((habit) => (
<StatsHabitCard
key={habit.id}
name={habit.name}
color={habit.color}
completedCount={habit.completedCount}
totalDays={days}
streak={habit.streak}
/>
))}
</div>
)}
</div>
</IonContent>
</IonPage>
);
};
export default Stats;