mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
feat(cycle): single-day read endpoint, symptom severity, and a richer log-day sheet
Add `GET /api/cycle/day-logs?date=YYYY-MM-DD` returning the full day-log DTO (or null) for one tz-anchored day so the web log-day sheet pre-fills instead of opening blank — a blank re-log no longer nulls untouched or HealthKit-sourced fields. Delete resolves the row id from the same read. Persist an optional 1-4 Likert severity per symptom link (migration 0133_v1150_cycle_symptom_severity), carried through the shared symptom-link writer and surfaced in the day-log DTO. Sheet now captures the previously missing fields — spotting between periods, pregnancy/progesterone home-test results, contraceptive kind — plus per-symptom severity chips, a one-tap end-period boundary, and a Delete action. The wheel ring marker sits at the 12-o'clock origin again (drop the double rotation), the cycle view goes two-column on desktop, and the empty ring carries a first-period call to action. New copy is localized across all six locales; the prediction and fertility surfaces keep their disclaimers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
824dcbb01a
commit
4b10bdc5da
23 files changed
+846
-143
No files matched your search
@@ -139,11 +139,7 @@ export const PATCH = apiHandler(
|
||||
|
||||
// Replace symptom links only when `symptoms` was supplied.
|
||||
if (body.symptoms !== undefined) {
|
||||
await replaceSymptomLinks(
|
||||
user.id,
|
||||
id,
|
||||
body.symptoms.map((s) => s.key),
|
||||
);
|
||||
await replaceSymptomLinks(user.id, id, body.symptoms);
|
||||
}
|
||||
|
||||
const row = await prisma.cycleDayLog.findUniqueOrThrow({
|
||||
|
||||
@@ -26,7 +26,10 @@ import {
|
||||
} from "@/lib/api-response";
|
||||
import { withIdempotency } from "@/lib/idempotency";
|
||||
import { requireCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { cycleDayLogInputSchema } from "@/lib/validations/cycle";
|
||||
import {
|
||||
cycleDayLogInputSchema,
|
||||
cycleDayLogQuerySchema,
|
||||
} from "@/lib/validations/cycle";
|
||||
import { upsertCycleDayLog } from "@/lib/cycle/day-log-write";
|
||||
import { findOwningCycleId } from "@/lib/cycle/cycle-attribution";
|
||||
import { toCycleDayLogDTO, dayLogSymptomInclude } from "@/lib/cycle/dto";
|
||||
@@ -34,6 +37,40 @@ import { DEFAULT_TIMEZONE } from "@/lib/mood/date-key";
|
||||
|
||||
export const POST = apiHandler(withIdempotency<[NextRequest]>(postDayLog));
|
||||
|
||||
/**
|
||||
* `GET /api/cycle/day-logs?date=YYYY-MM-DD` — the full `CycleDayLogDTO`
|
||||
* for one tz-anchored day, or `null` when nothing is logged. Lets the
|
||||
* log-day sheet pre-fill (no blank-sheet data loss) and Delete resolve the
|
||||
* row id. Gated + owner-scoped + `deletedAt:null`.
|
||||
*/
|
||||
export const GET = apiHandler(async (request: NextRequest) => {
|
||||
const { user } = await requireAuth();
|
||||
|
||||
const gate = await requireCycleEnabled(user.id, user.gender);
|
||||
if (!gate.enabled) return gate.response;
|
||||
|
||||
const parsed = cycleDayLogQuerySchema.safeParse({
|
||||
date: new URL(request.url).searchParams.get("date"),
|
||||
});
|
||||
if (!parsed.success) {
|
||||
return returnAllZodIssues(parsed.error, 422, {
|
||||
errorCode: "cycle.day-log.invalid",
|
||||
});
|
||||
}
|
||||
|
||||
const row = await prisma.cycleDayLog.findFirst({
|
||||
where: { userId: user.id, date: parsed.data.date, deletedAt: null },
|
||||
include: dayLogSymptomInclude,
|
||||
});
|
||||
|
||||
annotate({
|
||||
action: { name: "cycle.day-log.read", entity_type: "cycle_day_log" },
|
||||
meta: { found: row !== null },
|
||||
});
|
||||
|
||||
return apiSuccess(row ? toCycleDayLogDTO(row) : null);
|
||||
});
|
||||
|
||||
async function postDayLog(request: NextRequest): Promise<Response> {
|
||||
const { user } = await requireAuth();
|
||||
|
||||
|
||||
Reference in new issue
Block a user