+
{getIcon(activity.type)}
@@ -150,22 +188,20 @@ function ActivityItem({
{activity.type}
-
+
{formatActivityDetails(activity)}
{"endTime" in activity && activity.endTime && (
<>
-
+
{format(activity.endTime, "h:mm a")}
-
- ↑
-
+ ↓
>
)}
-
+
{format(activity.startTime, "h:mm a")}
diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx
new file mode 100644
index 0000000..7e0d491
--- /dev/null
+++ b/src/components/Stats.tsx
@@ -0,0 +1,208 @@
+import { useMemo } from "react";
+import { endOfDay, startOfDay } from "date-fns";
+import { useLiveQuery } from "dexie-react-hooks";
+import { db } from "../db";
+import { getColor, getIcon } from "../types/helpers";
+import type { Activity } from "../types/types";
+import { gramsToOz } from "../utils/convert";
+
+interface DayStats {
+ // Time-based totals (in minutes)
+ breast: number;
+ pump: number;
+ sleep: number;
+
+ // Volume totals (in oz)
+ volumePumped: number;
+ volumeDrank: number;
+
+ // Session counts
+ breastSessions: number;
+ pumpSessions: number;
+ sleepSessions: number;
+ bottleSessions: number;
+ diaperPee: number;
+ diaperPoop: number;
+ diaperBoth: number;
+}
+
+const calculateStats = (activities: Activity[]): DayStats => {
+ const stats: DayStats = {
+ breast: 0,
+ pump: 0,
+ sleep: 0,
+ volumePumped: 0,
+ volumeDrank: 0,
+ breastSessions: 0,
+ pumpSessions: 0,
+ sleepSessions: 0,
+ bottleSessions: 0,
+ diaperPee: 0,
+ diaperPoop: 0,
+ diaperBoth: 0,
+ };
+
+ activities.forEach((activity) => {
+ if (!activity.isComplete) return;
+
+ switch (activity.type) {
+ case "breast":
+ stats.breast += (activity.durationSeconds || 0) / 60;
+ stats.volumeDrank += gramsToOz(activity.volumeGrams || 0);
+ stats.breastSessions++;
+ break;
+ case "pump":
+ stats.pump += (activity.durationSeconds || 0) / 60;
+ stats.volumePumped += gramsToOz(activity.volumeGrams || 0);
+ stats.pumpSessions++;
+ break;
+ case "sleep":
+ stats.sleep += (activity.durationSeconds || 0) / 60;
+ stats.sleepSessions++;
+ break;
+ case "bottle":
+ stats.volumeDrank += gramsToOz(activity.volumeGrams);
+ stats.bottleSessions++;
+ break;
+ case "diaper":
+ if (activity.diaperType === "pee") stats.diaperPee++;
+ else if (activity.diaperType === "poop") stats.diaperPoop++;
+ else stats.diaperBoth++;
+ break;
+ }
+ });
+
+ return stats;
+};
+
+const formatDuration = (minutes: number): string => {
+ const hours = Math.floor(minutes / 60);
+ const mins = Math.round(minutes % 60);
+ if (hours > 0) return `${hours}h ${mins}m`;
+ return `${mins}m`;
+};
+
+export default function StatsPanel() {
+ const selectedDate = useMemo(() => new Date(), []); // TODO: Make this changeable
+
+ const activitiesQuery = useLiveQuery(
+ () =>
+ db.getActivitiesBetween(
+ startOfDay(selectedDate).getTime(),
+ endOfDay(selectedDate).getTime(),
+ ),
+ [selectedDate],
+ );
+ const activities = useMemo(() => activitiesQuery || [], [activitiesQuery]);
+
+ const stats = useMemo(
+ () => calculateStats(activities as Activity[]),
+ [activities],
+ );
+
+ return (
+
+
Today's Stats
+
+
+ {(stats.breast > 0 || stats.breastSessions > 0) && (
+
+ )}
+
+ {(stats.pump > 0 || stats.pumpSessions > 0) && (
+
+ )}
+
+ {(stats.sleep > 0 || stats.sleepSessions > 0) && (
+
+ )}
+
+ {/* Volume stats */}
+ {stats.volumePumped > 0 && (
+
+ )}
+
+ {stats.volumeDrank > 0 && (
+
0
+ ? `${stats.bottleSessions} bottle${stats.bottleSessions !== 1 ? "s" : ""}`
+ : undefined
+ }
+ />
+ )}
+
+ {/* Diaper stats */}
+ {(stats.diaperPee > 0 ||
+ stats.diaperPoop > 0 ||
+ stats.diaperBoth > 0) && (
+ 0 && `${stats.diaperPee} pee`,
+ stats.diaperPoop > 0 && `${stats.diaperPoop} poop`,
+ stats.diaperBoth > 0 && `${stats.diaperBoth} both`,
+ ]
+ .filter(Boolean)
+ .join(" • ")}
+ />
+ )}
+
+ {activities.length === 0 && (
+
+ No activities today
+
+ )}
+
+
+ );
+}
+
+function StatCard({
+ type,
+ label,
+ primary,
+ secondary,
+}: {
+ type: string;
+ label: string;
+ primary: string;
+ secondary?: string;
+}) {
+ return (
+
+
+ {getIcon(type)}
+
+
+
{label}
+
{primary}
+ {secondary &&
{secondary}
}
+
+
+ );
+}
diff --git a/src/db.ts b/src/db.ts
index 636b062..0010256 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -29,6 +29,16 @@ export class ActivityDB extends Dexie {
// if (data.id) return this.activities.update(data.id, data);
// return this.activities.add(data);
// }
+ async getActivitiesBetween(
+ startTime: number,
+ endTime: number,
+ ): Promise
{
+ return this.activities
+ .where("startTime")
+ .between(startTime, endTime, true, true)
+ .reverse()
+ .sortBy("startTime");
+ }
async deleteActivity(id: number): Promise {
await this.activities.delete(id);