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 + - + +
+ +
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;