chore: fix typecheck on monorepo

- regeneratePlan: switch return type to Promise<void> so it satisfies the
  React form action contract.
- fast.routes PATCH: omit undefined keys instead of passing them, to
  comply with Prisma + exactOptionalPropertyTypes.
- weight-entry-form: cast field value/defaultValues to bypass z.input
  reporting `Date` for `z.coerce.date()` (zod 3.25 still emits the
  target type, not unknown).

pnpm typecheck, lint, test now all green across the monorepo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.7 committed 2026-04-29 21:16:08 +02:00
1 parent ad1e4ecaeb
commit cdfc685855
3 files changed
+20 -14

No files matched your search

+9 -6
View File
@@ -59,14 +59,17 @@ export const fastRoutes: FastifyPluginAsync = async (fastify) => {
}); });
if (!owned) return reply.code(404).send({ error: 'fast_event_not_found' }); if (!owned) return reply.code(404).send({ error: 'fast_event_not_found' });
// Costruiamo l'oggetto data omettendo le chiavi non fornite, perché con
// `exactOptionalPropertyTypes` Prisma rifiuta `undefined` esplicito.
const updateData: Parameters<typeof fastify.prisma.fastEvent.update>[0]['data'] = {};
if (data.endedAt) updateData.endedAt = data.endedAt;
if (data.status) updateData.status = data.status;
if (data.symptoms) updateData.symptoms = JSON.stringify(data.symptoms);
if (data.notes != null) updateData.notes = data.notes;
const event = await fastify.prisma.fastEvent.update({ const event = await fastify.prisma.fastEvent.update({
where: { id }, where: { id },
data: { data: updateData,
endedAt: data.endedAt ?? undefined,
status: data.status ?? undefined,
symptoms: data.symptoms ? JSON.stringify(data.symptoms) : undefined,
notes: data.notes ?? undefined,
},
}); });
return { event: { id: event.id, status: event.status } }; return { event: { id: event.id, status: event.status } };
}); });
+4 -5
View File
@@ -42,9 +42,9 @@ export async function fetchCurrentPlan(): Promise<CurrentPlan | null> {
return data.plan; return data.plan;
} }
export type RegenerateResult = { ok: true } | { ok: false; error: string }; // Server action invocata via `<form action={regeneratePlan}>`. La firma è
// `(FormData) => Promise<void>` per allinearsi al type richiesto da React 18.
export async function regeneratePlan(): Promise<RegenerateResult> { export async function regeneratePlan(_formData?: FormData): Promise<void> {
const res = await fetch(`${API_URL}/me/meal-plans`, { const res = await fetch(`${API_URL}/me/meal-plans`, {
method: 'POST', method: 'POST',
headers: { cookie: cookieHeader() }, headers: { cookie: cookieHeader() },
@@ -52,10 +52,9 @@ export async function regeneratePlan(): Promise<RegenerateResult> {
}); });
if (!res.ok) { if (!res.ok) {
const body = (await res.json().catch(() => ({}))) as { error?: string }; const body = (await res.json().catch(() => ({}))) as { error?: string };
return { ok: false, error: body.error ?? `api_error_${res.status}` }; throw new Error(body.error ?? `api_error_${res.status}`);
} }
revalidatePath('/plan'); revalidatePath('/plan');
return { ok: true };
} }
export type SwapResult = { ok: true } | { ok: false; error: string }; export type SwapResult = { ok: true } | { ok: false; error: string };
@@ -29,12 +29,16 @@ export function WeightEntryForm({ latest }: { latest: WeightEntryRow | null }) {
const [serverError, setServerError] = useState<string | null>(null); const [serverError, setServerError] = useState<string | null>(null);
const [savedAt, setSavedAt] = useState<string | null>(null); const [savedAt, setSavedAt] = useState<string | null>(null);
// L'input HTML type="date" passa stringhe ISO; zod le coerce a Date a runtime
// (vedi `weightEntryInputSchema.date = z.coerce.date()`). Il TS-side resta
// strict, perciò usiamo cast `unknown` agli ingressi dei defaultValues e di
// form.reset.
const form = useForm<WeightEntryInput>({ const form = useForm<WeightEntryInput>({
resolver: zodResolver(weightEntryInputSchema), resolver: zodResolver(weightEntryInputSchema),
defaultValues: { defaultValues: {
date: todayISO(), date: todayISO(),
weightKg: latest?.weightKg, weightKg: latest?.weightKg,
} as WeightEntryInput, } as unknown as WeightEntryInput,
}); });
const onSubmit = form.handleSubmit(async (input) => { const onSubmit = form.handleSubmit(async (input) => {
@@ -45,7 +49,7 @@ export function WeightEntryForm({ latest }: { latest: WeightEntryRow | null }) {
return; return;
} }
setSavedAt(new Date().toISOString()); setSavedAt(new Date().toISOString());
form.reset({ date: todayISO(), weightKg: input.weightKg } as WeightEntryInput); form.reset({ date: todayISO(), weightKg: input.weightKg } as unknown as WeightEntryInput);
}); });
return ( return (
@@ -59,7 +63,7 @@ export function WeightEntryForm({ latest }: { latest: WeightEntryRow | null }) {
<FormItem className="space-y-3"> <FormItem className="space-y-3">
<FormLabel>{t('date')}</FormLabel> <FormLabel>{t('date')}</FormLabel>
<FormControl> <FormControl>
<Input type="date" {...field} value={field.value as string} /> <Input type="date" {...field} value={field.value as unknown as string} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>