feat(health): health card share link, PDF export, weight chart in card

- ShareDialog: expiry selector (7/14/30/60/90 days, default 14), shows
  expiry date after generation, "Als PDF öffnen / drucken" button
- WeightChart extracted to standalone component (WeightChart.tsx) so it
  can be shared between HealthDashboard and the public health card page
- Health card page: renders WeightChart when 2+ weight entries exist
- AutoPrint client component: auto-triggers window.print() when card is
  opened with ?print=1 (600ms delay for render to settle)
- README: Phase 6 + Phase 7 marked complete with full feature summaries;
  project structure and router list updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Sonnet 4.6 committed 2026-06-15 06:06:28 +02:00
1 parent b9f786c87f
commit 80559c3b3f
12 files changed
+780 -371

No files matched your search

+24 -8
View File
@@ -135,27 +135,43 @@ export const healthRouter = router({
// ── Health Card ──────────────────────────────────────────────────────────────
getOrCreateCard: protectedProcedure
.input(z.object({ petId: z.string().min(1) }))
.input(z.object({
petId: z.string().min(1),
expiryDays: z.union([
z.literal(7), z.literal(14), z.literal(30),
z.literal(60), z.literal(90),
]).default(14),
}))
.mutation(async ({ ctx, input }) => {
await assertOwner(ctx.prisma, input.petId, ctx.userId);
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + input.expiryDays);
const card = await ctx.prisma.healthCard.upsert({
where: { petId: input.petId },
update: {},
create: { petId: input.petId },
update: { expiresAt },
create: { petId: input.petId, expiresAt },
});
return { token: card.token };
return { token: card.token, expiresAt: card.expiresAt };
}),
regenerateCard: protectedProcedure
.input(z.object({ petId: z.string().min(1) }))
.input(z.object({
petId: z.string().min(1),
expiryDays: z.union([
z.literal(7), z.literal(14), z.literal(30),
z.literal(60), z.literal(90),
]).default(14),
}))
.mutation(async ({ ctx, input }) => {
await assertOwner(ctx.prisma, input.petId, ctx.userId);
const newToken = crypto.randomUUID().replace(/-/g, "");
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + input.expiryDays);
const card = await ctx.prisma.healthCard.upsert({
where: { petId: input.petId },
update: { token: newToken },
create: { petId: input.petId, token: newToken },
update: { token: newToken, expiresAt },
create: { petId: input.petId, token: newToken, expiresAt },
});
return { token: card.token };
return { token: card.token, expiresAt: card.expiresAt };
}),
});