feat: platform backbone and field capture, first part (checkpoint)

Platform backbone (plan 2026-10-13-plattform-rueckgrat), reviewed tasks:
- run roles api/worker/job/all with shared startup checks (Task 1)
- schema and migration for DomainEvent, deliveries, JobRun, ThrottlerHit (Task 2)
- audit, notifications and domain events written in the same transaction (Task 3)
- dispatcher with SKIP LOCKED claims, retries and stale release (Task 4)
- worker building blocks: tick, LISTEN/NOTIFY wake-up, health (Task 5)
- worker process and entry points (Task 6)
- throttler counters in Postgres (Task 19)
- separate keys per purpose with key ring (Task 20)
- public origin page without exact location (Task 22)
- shop keys split into read and write (Task 23)
- Kustomize base, components, overlay encry, CI validation (Task 26)

Field capture T3 (plan 2026-10-13-t3-erfassung-am-stand), reviewed tasks:
- brood frames, read endpoints, apiary overview and offline pack
- network basics, offline queue v3, navigation sheets, form building blocks
- new inspection with last-time hints and weather, varroa per day,
  treatment prefill, apiary page with round
- inspection photo (Task 10) is implemented, review pending

Not rolled out. Remaining tasks follow in later commits.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-13 17:57:54 +02:00
1 parent 10cbe9bf2d
commit c01a38835a
340 files changed
+25695 -1808

No files matched your search

@@ -0,0 +1,11 @@
import { apiFetch } from "@/lib/auth/api-client";
import type { ColonyOverview } from "./field-types";
export function fetchColonyOverview(apiaryId: string): Promise<ColonyOverview> {
return apiFetch<ColonyOverview>(`/apiaries/${encodeURIComponent(apiaryId)}/colony-overview`);
}
/** Query key of the apiary page's colony overview. */
export function colonyOverviewKey(apiaryId: string): readonly ["apiaries", string, "colony-overview"] {
return ["apiaries", apiaryId, "colony-overview"] as const;
}
@@ -0,0 +1,83 @@
import type { ApiaryStatus } from "./types";
import type { Colony, ColonyCondition, ColonyStatus } from "@/lib/colonies/types";
import type { Inspection } from "@/lib/inspections/types";
import type { Queen } from "@/lib/queens/types";
import type { Task } from "@/lib/tasks/types";
// Mirrors ApiCore-Backend/src/apiaries/apiary-field.types.ts - keep both in step.
/** One living colony on the apiary page, reduced to what the traffic light needs. */
export interface ColonyOverviewRow {
id: string;
code: string;
status: ColonyStatus;
strength: number | null;
broodStatus: ColonyCondition | null;
feedStatus: ColonyCondition | null;
healthStatus: ColonyCondition | null;
/** ISO timestamp; always null when the caller may not read inspections. */
lastInspectionDate: string | null;
/** Unread WARNUNG notifications visible to the caller. */
openWarnings: number;
/** Unread KRITISCH notifications visible to the caller. */
openCritical: number;
}
export interface ColonyOverview {
apiaryId: string;
inspectionsVisible: boolean;
rows: ColonyOverviewRow[];
}
/**
* Everything the offline apiary mode needs. It deliberately carries no
* coordinates, address or apiary notes (apiary locations are theft-sensitive)
* and no inspection userId. Sections the caller has no read permission for
* are null.
*/
export interface OfflinePackV1 {
version: 1;
generatedAt: string;
apiary: {
id: string;
name: string;
location: string | null;
status: ApiaryStatus;
};
overview: ColonyOverview;
colonies: Pick<
Colony,
| "id"
| "code"
| "apiaryId"
| "status"
| "colonyType"
| "hiveType"
| "strength"
| "broodStatus"
| "feedStatus"
| "healthStatus"
| "currentQueenId"
| "notes"
>[];
queens:
| Pick<
Queen,
"id" | "code" | "hatchYear" | "breed" | "markingColor" | "markingNumber" | "status"
>[]
| null;
inspections: Omit<Inspection, "userId">[] | null;
tasks:
| Pick<
Task,
| "id"
| "title"
| "description"
| "dueDate"
| "priority"
| "status"
| "relatedType"
| "relatedId"
>[]
| null;
}
@@ -10,6 +10,8 @@ export interface Apiary {
address: string | null;
status: ApiaryStatus;
notes: string | null;
/** Shown publicly on the origin page instead of the exact location. */
publicLocationLabel: string | null;
createdAt: string;
updatedAt: string;
deactivatedAt: string | null;
@@ -24,6 +26,7 @@ export interface ApiaryInput {
longitude?: number | null;
address?: string | null;
notes?: string | null;
publicLocationLabel?: string | null;
}
/** `null` clears an optional field, an omitted one stays unchanged. */
@@ -35,4 +38,5 @@ export interface UpdateApiaryInput {
longitude?: number | null;
address?: string | null;
notes?: string | null;
publicLocationLabel?: string | null;
}
@@ -0,0 +1,13 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { colonyOverviewKey, fetchColonyOverview } from "./field-api";
/** The apiary's living colonies with their traffic-light inputs. */
export function useColonyOverview(apiaryId: string, enabled = true) {
return useQuery({
queryKey: colonyOverviewKey(apiaryId),
queryFn: () => fetchColonyOverview(apiaryId),
enabled,
});
}