feat: T4 wave 4 - rules suggest, takeover, chains, autopilot A1-A3, suggestions page, varroa bands UI

- Automation rules propose suggestions instead of creating tasks;
  notify per rule config; corrected varroa measurements refresh or
  resolve their suggestion
- Inspection values can be taken over into the colony card
  (occupiedFrames 0-40, strength deprecated); warning counts use the
  same visibility rule as the notification list
- Biotech chains: swarm check after queen cells, queen acceptance
  check, split schedules (raise own, queen cell, queen)
- Season autopilot core with A1 inspection due, A2 varroa measurement,
  A3 recheck after treatment; colony sync and daily step
- Frontend: suggestions page with groups, bulk accept, undo, paging,
  focus management and permission gating
- Frontend: varroa traffic light from seasonal bands, sample size,
  one range request for the colony chart
- Notifications: mark-read answers 404 for rows the user may no
  longer see

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-14 15:22:36 +02:00
1 parent 64a2b7dad9
commit 02eb16924e
142 files changed
+11847 -1251

No files matched your search

@@ -0,0 +1,80 @@
import {
Bug,
CalendarCheck,
ClipboardCheck,
Crown,
Droplet,
Eraser,
HeartPulse,
RefreshCw,
ShoppingCart,
Snowflake,
Split,
Sprout,
Sun,
Syringe,
UserCheck,
Utensils,
type LucideIcon,
} from "lucide-react";
import type { SuggestionIntent } from "./types";
/** Used for combined rows („Durchsicht fällig · 6 Völker“) and as the icon's name. */
export const INTENT_LABELS: Record<SuggestionIntent, string> = {
INSPECT: "Durchsicht fällig",
SWARM_CHECK: "Schwarmkontrolle",
QUEEN_CHECK: "Königin prüfen",
QUEEN_ACCEPTANCE: "Zusetzen prüfen",
QUEEN_REPLACE: "Königin ersetzen",
SPLIT_PLAN: "Ableger prüfen",
VARROA_MEASURE: "Varroa messen",
VARROA_RECHECK: "Varroa nachkontrollieren",
VARROA_TREAT: "Behandlung prüfen",
SUMMER_TREATMENT: "Sommerbehandlung",
FEED: "Füttern",
HONEY_SUPER: "Honigraum prüfen",
WINTER_CHECK: "Winterkontrolle",
HEALTH_CHECK: "Gesundheit prüfen",
FOLLOW_UP: "Nachkontrolle",
STOCK_REORDER: "Nachbestellen",
CLEANUP: "Aufräumen",
};
export const INTENT_ICONS: Record<SuggestionIntent, LucideIcon> = {
INSPECT: ClipboardCheck,
SWARM_CHECK: Sprout,
QUEEN_CHECK: Crown,
QUEEN_ACCEPTANCE: UserCheck,
QUEEN_REPLACE: RefreshCw,
SPLIT_PLAN: Split,
VARROA_MEASURE: Bug,
VARROA_RECHECK: Bug,
VARROA_TREAT: Syringe,
SUMMER_TREATMENT: Sun,
FEED: Utensils,
HONEY_SUPER: Droplet,
WINTER_CHECK: Snowflake,
HEALTH_CHECK: HeartPulse,
FOLLOW_UP: CalendarCheck,
STOCK_REORDER: ShoppingCart,
CLEANUP: Eraser,
};
/** The form a DO suggestion („Erfassen“) opens; the inspection form for the rest. */
const CAPTURE_PATHS: Partial<Record<SuggestionIntent, string>> = {
VARROA_MEASURE: "/varroa/new",
VARROA_RECHECK: "/varroa/new",
FEED: "/feedings/new",
WINTER_CHECK: "/winter",
};
/** Winter checks are entered from the winter page, which has no colony parameter. */
const PATHS_WITHOUT_COLONY = new Set(["/winter"]);
const DEFAULT_CAPTURE_PATH = "/inspections/new";
export function captureHref(intent: SuggestionIntent, colonyId: string | null): string {
const path = CAPTURE_PATHS[intent] ?? DEFAULT_CAPTURE_PATH;
if (!colonyId || PATHS_WITHOUT_COLONY.has(path)) return path;
return `${path}?colonyId=${encodeURIComponent(colonyId)}`;
}