diff --git a/ApiCore-Frontend/src/app/(app)/inspections/new/page.tsx b/ApiCore-Frontend/src/app/(app)/inspections/new/page.tsx
index 3c5c698..cc0e54c 100644
--- a/ApiCore-Frontend/src/app/(app)/inspections/new/page.tsx
+++ b/ApiCore-Frontend/src/app/(app)/inspections/new/page.tsx
@@ -40,8 +40,11 @@ export default function NewInspectionPage() {
const [date, setDate] = useState(todayIsoDate());
const [colonyStrength, setColonyStrength] = useState("");
const [queenSeen, setQueenSeen] = useState(false);
+ const [eggsPresent, setEggsPresent] = useState(true);
const [queenCellsPresent, setQueenCellsPresent] = useState(false);
- const [feedStatusOk, setFeedStatusOk] = useState(true);
+ const [feedStatus, setFeedStatus] = useState<"ausreichend" | "niedrig" | "kritisch">(
+ "ausreichend",
+ );
const [healthIssues, setHealthIssues] = useState("");
const [varroaIssues, setVarroaIssues] = useState("");
const [measures, setMeasures] = useState("");
@@ -74,8 +77,9 @@ export default function NewInspectionPage() {
date: new Date(date).toISOString(),
colonyStrength: colonyStrength ? Number(colonyStrength) : undefined,
queenSeen,
+ eggsPresent,
queenCellsPresent,
- feedStatus: feedStatusOk ? "ausreichend" : "niedrig",
+ feedStatus,
healthIssues: healthIssues || undefined,
varroaIssues: varroaIssues || undefined,
measures: measures || undefined,
@@ -142,6 +146,19 @@ export default function NewInspectionPage() {
/>
Königin gesehen
+
Weiselzellen vorhanden
-
+
+
+
+
diff --git a/ApiCore-Frontend/src/app/(app)/settings/automation-rules/page.tsx b/ApiCore-Frontend/src/app/(app)/settings/automation-rules/page.tsx
new file mode 100644
index 0000000..ca2293b
--- /dev/null
+++ b/ApiCore-Frontend/src/app/(app)/settings/automation-rules/page.tsx
@@ -0,0 +1,136 @@
+"use client";
+
+import { useState } from "react";
+import { Zap } from "lucide-react";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
+import { fetchAutomationRules, updateAutomationRule } from "@/lib/automation/api";
+import { AutomationRule, AutomationTriggerType } from "@/lib/automation/types";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+
+const TRIGGER_LABELS: Record
= {
+ QUEENLESS_NO_EGGS: "Keine Königin gesehen & keine Stifte",
+ VARROA_THRESHOLD: "Varroa-Schwellenwert überschritten",
+ FEED_STATUS_CRITICAL: "Futterstatus kritisch",
+};
+
+const TRIGGER_DESCRIPTIONS: Record = {
+ QUEENLESS_NO_EGGS:
+ "Bei einer Durchsicht: Königin nicht gesehen und keine Stifte vorhanden → Kontrollaufgabe.",
+ VARROA_THRESHOLD:
+ "Milbenzahl (bzw. Milbenfall pro Tag) über dem Schwellenwert → Behandlung prüfen.",
+ FEED_STATUS_CRITICAL:
+ "Futterstatus bei einer Durchsicht auf „kritisch“ gesetzt → Fütterungsaufgabe.",
+};
+
+function ruleThreshold(rule: AutomationRule): string {
+ const config = rule.config as { thresholdMitesPerDay?: number } | null;
+ return String(config?.thresholdMitesPerDay ?? "");
+}
+
+function RuleCard({ rule }: { rule: AutomationRule }) {
+ const queryClient = useQueryClient();
+ const [actionTemplate, setActionTemplate] = useState(rule.actionTemplate ?? "");
+ const [threshold, setThreshold] = useState(ruleThreshold(rule));
+
+ const mutation = useMutation({
+ mutationFn: (input: { active?: boolean; config?: Record; actionTemplate?: string }) =>
+ updateAutomationRule(rule.id, input),
+ onSuccess: () => {
+ void queryClient.invalidateQueries({ queryKey: ["automation-rules"] });
+ toast.success("Regel gespeichert");
+ },
+ onError: () => toast.error("Regel konnte nicht gespeichert werden"),
+ });
+
+ function handleSave() {
+ const input: { config?: Record; actionTemplate?: string } = {
+ actionTemplate: actionTemplate || undefined,
+ };
+ if (rule.triggerType === "VARROA_THRESHOLD") {
+ input.config = { thresholdMitesPerDay: Number(threshold) || 10 };
+ }
+ mutation.mutate(input);
+ }
+
+ return (
+
+
+ {TRIGGER_LABELS[rule.triggerType]}
+
+ {rule.active ? "Aktiv" : "Inaktiv"}
+
+
+
+ {TRIGGER_DESCRIPTIONS[rule.triggerType]}
+
+
+ setActionTemplate(event.target.value)}
+ />
+
+ {rule.triggerType === "VARROA_THRESHOLD" ? (
+
+
+ setThreshold(event.target.value)}
+ />
+
+ ) : null}
+
+
+
+
+
+
+ );
+}
+
+export default function AutomationRulesPage() {
+ const { data: rules, isLoading, isError } = useQuery({
+ queryKey: ["automation-rules"],
+ queryFn: fetchAutomationRules,
+ });
+
+ return (
+
+
+
+ {isLoading ? Lädt…
: null}
+ {isError ? (
+
+ Automatisierungen konnten nicht geladen werden.
+
+ ) : null}
+
+ {rules?.map((rule) => (
+
+ ))}
+
+ );
+}
diff --git a/ApiCore-Frontend/src/app/(app)/sidebar.tsx b/ApiCore-Frontend/src/app/(app)/sidebar.tsx
index 64db8fd..c5d0bfc 100644
--- a/ApiCore-Frontend/src/app/(app)/sidebar.tsx
+++ b/ApiCore-Frontend/src/app/(app)/sidebar.tsx
@@ -22,6 +22,7 @@ import {
Warehouse,
Webhook,
Wrench,
+ Zap,
} from "lucide-react";
import { cn } from "@/lib/utils";
import type { AuthUser } from "@/lib/auth/auth-context";
@@ -82,6 +83,7 @@ const ADMIN_GROUP: NavGroup = {
{ label: "Benutzer", href: "/settings/users", icon: Users },
{ label: "Audit-Log", href: "/settings/audit-log", icon: History },
{ label: "Webhooks", href: "/settings/webhooks", icon: Webhook },
+ { label: "Automatisierungen", href: "/settings/automation-rules", icon: Zap },
],
};
diff --git a/ApiCore-Frontend/src/lib/automation/api.ts b/ApiCore-Frontend/src/lib/automation/api.ts
new file mode 100644
index 0000000..057f5b0
--- /dev/null
+++ b/ApiCore-Frontend/src/lib/automation/api.ts
@@ -0,0 +1,16 @@
+import { apiFetch } from "@/lib/auth/api-client";
+import { AutomationRule, UpdateAutomationRuleInput } from "./types";
+
+export function fetchAutomationRules(): Promise {
+ return apiFetch("/automation-rules");
+}
+
+export function updateAutomationRule(
+ id: string,
+ input: UpdateAutomationRuleInput,
+): Promise {
+ return apiFetch(`/automation-rules/${id}`, {
+ method: "PATCH",
+ body: JSON.stringify(input),
+ });
+}
diff --git a/ApiCore-Frontend/src/lib/automation/types.ts b/ApiCore-Frontend/src/lib/automation/types.ts
new file mode 100644
index 0000000..2819259
--- /dev/null
+++ b/ApiCore-Frontend/src/lib/automation/types.ts
@@ -0,0 +1,20 @@
+export type AutomationTriggerType =
+ | "QUEENLESS_NO_EGGS"
+ | "VARROA_THRESHOLD"
+ | "FEED_STATUS_CRITICAL";
+
+export interface AutomationRule {
+ id: string;
+ triggerType: AutomationTriggerType;
+ active: boolean;
+ config: Record | null;
+ actionTemplate: string | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface UpdateAutomationRuleInput {
+ active?: boolean;
+ config?: Record;
+ actionTemplate?: string;
+}
diff --git a/ApiCore-Frontend/src/lib/inspections/types.ts b/ApiCore-Frontend/src/lib/inspections/types.ts
index 3267586..55e496b 100644
--- a/ApiCore-Frontend/src/lib/inspections/types.ts
+++ b/ApiCore-Frontend/src/lib/inspections/types.ts
@@ -32,6 +32,7 @@ export interface CreateInspectionInput {
temperature?: number;
colonyStrength?: number;
queenSeen?: boolean;
+ eggsPresent?: boolean;
queenCellsPresent?: boolean;
feedStatus?: string;
healthIssues?: string;