mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
feat(activity): add an activity calendar with personal baseline and goals
A week or month of steps, active minutes, workouts by sport and training load, each day placed against the person's own spread over the 84 days before the period and each period against the median of the twelve weeks or six months before it. Optional weekly and monthly goals live on the account (activity_goals_json), travel with the backup and are cleared by a data wipe. The calendar sits on the workouts page; the steps page links to it. Computed server-side without AI.
This commit is contained in:
1 parent
e1b70537cc
commit
5eb7caebae
40 files changed
+4172
-6
No files matched your search
@@ -61,6 +61,7 @@ import {
|
||||
import { UNREADABLE_EXPORT_MARKER } from "@/lib/export/unreadable-marker";
|
||||
import { locales } from "@/lib/i18n/config";
|
||||
import { thresholdsUpdateSchema } from "@/lib/validations/thresholds";
|
||||
import { activityGoalsSchema } from "@/lib/validations/activity-goals";
|
||||
import { isPublicUrl } from "@/lib/validations/notifications";
|
||||
import {
|
||||
glucoseUnitPatchSchema,
|
||||
@@ -267,6 +268,37 @@ const thresholds: SettingCodec = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The activity goals, goal by goal. A goal outside its bounds, or a key this
|
||||
* release does not know, is dropped and named; the valid goals beside it
|
||||
* still come back.
|
||||
*/
|
||||
const activityGoals: SettingCodec = {
|
||||
write: (row) => ({ activityGoalsJson: row.activityGoalsJson ?? null }),
|
||||
read: (entry) => {
|
||||
if (!has(entry, "activityGoalsJson")) return undefined;
|
||||
const value = entry.activityGoalsJson;
|
||||
if (value === null) return Prisma.DbNull;
|
||||
if (typeof value !== "object" || Array.isArray(value)) {
|
||||
return new Refused("activityGoalsJson");
|
||||
}
|
||||
const kept: Record<string, number> = {};
|
||||
const refused: string[] = [];
|
||||
for (const [key, goal] of Object.entries(value)) {
|
||||
const parsed = activityGoalsSchema.safeParse({ [key]: goal });
|
||||
if (!parsed.success) {
|
||||
refused.push(`activityGoalsJson.${key}`);
|
||||
} else if (typeof goal === "number") {
|
||||
kept[key] = goal;
|
||||
}
|
||||
}
|
||||
return new PartlyRefused(
|
||||
Object.keys(kept).length === 0 ? Prisma.DbNull : kept,
|
||||
refused,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const injectionSites = Object.values(InjectionSite) as string[];
|
||||
|
||||
/** The injection sites to avoid everywhere; an unknown site is dropped. */
|
||||
@@ -505,6 +537,7 @@ const ACCOUNT_SETTING_CODECS: Readonly<
|
||||
documentsLayoutJson: json("documentsLayoutJson"),
|
||||
moduleListLayoutJson: json("moduleListLayoutJson"),
|
||||
previewFeaturesJson: json("previewFeaturesJson"),
|
||||
activityGoalsJson: activityGoals,
|
||||
reportSelectionJson: json("reportSelectionJson"),
|
||||
globalExcludedInjectionSites: excludedSites,
|
||||
healthKitConfigJson: json("healthKitConfigJson"),
|
||||
|
||||
@@ -1015,6 +1015,7 @@ export const USER_COLUMN_BACKUP_CLASS = {
|
||||
documentsLayoutJson: "SETTING",
|
||||
moduleListLayoutJson: "SETTING",
|
||||
previewFeaturesJson: "SETTING",
|
||||
activityGoalsJson: "SETTING",
|
||||
reportSelectionJson: "SETTING",
|
||||
globalExcludedInjectionSites: "SETTING",
|
||||
healthKitConfigJson: "SETTING",
|
||||
|
||||
Reference in new issue
Block a user