feat(frontend): Suche und Filter (Modul 14)
/search-Seite über Bienenstände, Völker, Königinnen, Aufgaben und Honigchargen. Filterleisten (Status/Standort) in den Listen-Views für Bienenstände, Völker, Königinnen und Honigchargen.
This commit is contained in:
1 parent
146b56d683
commit
166979ff3c
21 files changed
+314
-28
No files matched your search
@@ -1,10 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { fetchApiaries } from "@/lib/apiaries/api";
|
import { fetchApiaries } from "@/lib/apiaries/api";
|
||||||
|
import { ApiaryStatus } from "@/lib/apiaries/types";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -15,9 +25,12 @@ import {
|
|||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
export default function ApiariesPage() {
|
export default function ApiariesPage() {
|
||||||
|
const [status, setStatus] = useState<ApiaryStatus | "">("");
|
||||||
|
const [location, setLocation] = useState("");
|
||||||
|
|
||||||
const { data: apiaries, isLoading, isError } = useQuery({
|
const { data: apiaries, isLoading, isError } = useQuery({
|
||||||
queryKey: ["apiaries"],
|
queryKey: ["apiaries", status, location],
|
||||||
queryFn: fetchApiaries,
|
queryFn: () => fetchApiaries({ status: status || undefined, location: location || undefined }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,6 +42,28 @@ export default function ApiariesPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<Select
|
||||||
|
value={status || "ALL"}
|
||||||
|
onValueChange={(value) => setStatus(value === "ALL" ? "" : ((value ?? "") as ApiaryStatus))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-40">
|
||||||
|
<SelectValue placeholder="Status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="ALL">Alle Status</SelectItem>
|
||||||
|
<SelectItem value="ACTIVE">Aktiv</SelectItem>
|
||||||
|
<SelectItem value="INACTIVE">Inaktiv</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
className="w-48"
|
||||||
|
placeholder="Standort"
|
||||||
|
value={location}
|
||||||
|
onChange={(event) => setLocation(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-red-600">
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function MoveDialog({ colony }: { colony: Colony }) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [apiaryId, setApiaryId] = useState("");
|
const [apiaryId, setApiaryId] = useState("");
|
||||||
const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: fetchApiaries });
|
const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: () => fetchApiaries() });
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: () => moveColony(colony.id, { apiaryId }),
|
mutationFn: () => moveColony(colony.id, { apiaryId }),
|
||||||
@@ -182,7 +182,7 @@ function MergeDialog({ colony }: { colony: Colony }) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [mergedColonyId, setMergedColonyId] = useState("");
|
const [mergedColonyId, setMergedColonyId] = useState("");
|
||||||
const { data: colonies } = useQuery({ queryKey: ["colonies"], queryFn: fetchColonies });
|
const { data: colonies } = useQuery({ queryKey: ["colonies"], queryFn: () => fetchColonies() });
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: () => mergeColony(colony.id, { mergedColonyIds: [mergedColonyId] }),
|
mutationFn: () => mergeColony(colony.id, { mergedColonyIds: [mergedColonyId] }),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const COLONY_TYPES: ColonyType[] = [
|
|||||||
export default function NewColonyPage() {
|
export default function NewColonyPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: fetchApiaries });
|
const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: () => fetchApiaries() });
|
||||||
|
|
||||||
const [code, setCode] = useState("");
|
const [code, setCode] = useState("");
|
||||||
const [apiaryId, setApiaryId] = useState("");
|
const [apiaryId, setApiaryId] = useState("");
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { fetchColonies } from "@/lib/colonies/api";
|
import { fetchColonies } from "@/lib/colonies/api";
|
||||||
|
import { ColonyStatus } from "@/lib/colonies/types";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -14,10 +23,23 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
|
const STATUS_OPTIONS: ColonyStatus[] = [
|
||||||
|
"ACTIVE",
|
||||||
|
"WEAK",
|
||||||
|
"CONSPICUOUS",
|
||||||
|
"QUEENLESS",
|
||||||
|
"DISSOLVED",
|
||||||
|
"SOLD",
|
||||||
|
"DECEASED",
|
||||||
|
"MERGED",
|
||||||
|
];
|
||||||
|
|
||||||
export default function ColoniesPage() {
|
export default function ColoniesPage() {
|
||||||
|
const [status, setStatus] = useState<ColonyStatus | "">("");
|
||||||
|
|
||||||
const { data: colonies, isLoading, isError } = useQuery({
|
const { data: colonies, isLoading, isError } = useQuery({
|
||||||
queryKey: ["colonies"],
|
queryKey: ["colonies", status],
|
||||||
queryFn: fetchColonies,
|
queryFn: () => fetchColonies({ status: status || undefined }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,6 +51,23 @@ export default function ColoniesPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={status || "ALL"}
|
||||||
|
onValueChange={(value) => setStatus(value === "ALL" ? "" : ((value ?? "") as ColonyStatus))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-40">
|
||||||
|
<SelectValue placeholder="Status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="ALL">Alle Status</SelectItem>
|
||||||
|
{STATUS_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-red-600">
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function NewFeedingPage() {
|
|||||||
|
|
||||||
const { data: colonies } = useQuery({
|
const { data: colonies } = useQuery({
|
||||||
queryKey: ["colonies"],
|
queryKey: ["colonies"],
|
||||||
queryFn: fetchColonies,
|
queryFn: () => fetchColonies(),
|
||||||
});
|
});
|
||||||
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export default function NewHarvestPage() {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: fetchApiaries });
|
const { data: apiaries } = useQuery({ queryKey: ["apiaries"], queryFn: () => fetchApiaries() });
|
||||||
const { data: colonies } = useQuery({ queryKey: ["colonies"], queryFn: fetchColonies });
|
const { data: colonies } = useQuery({ queryKey: ["colonies"], queryFn: () => fetchColonies() });
|
||||||
|
|
||||||
const [apiaryId, setApiaryId] = useState(searchParams.get("apiaryId") ?? "");
|
const [apiaryId, setApiaryId] = useState(searchParams.get("apiaryId") ?? "");
|
||||||
const [date, setDate] = useState(todayIsoDate());
|
const [date, setDate] = useState(todayIsoDate());
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { fetchHoneyBatches } from "@/lib/honey-batches/api";
|
import { fetchHoneyBatches } from "@/lib/honey-batches/api";
|
||||||
|
import { HoneyBatchStatus } from "@/lib/honey-batches/types";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -14,10 +23,23 @@ import {
|
|||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { CreateHoneyBatchDialog } from "./create-honey-batch-dialog";
|
import { CreateHoneyBatchDialog } from "./create-honey-batch-dialog";
|
||||||
|
|
||||||
|
const STATUS_OPTIONS: HoneyBatchStatus[] = [
|
||||||
|
"RAW",
|
||||||
|
"EXTRACTED",
|
||||||
|
"CLARIFIED",
|
||||||
|
"CREAMED",
|
||||||
|
"STORED",
|
||||||
|
"BOTTLED",
|
||||||
|
"SOLD",
|
||||||
|
"CONSUMED",
|
||||||
|
];
|
||||||
|
|
||||||
export default function HoneyBatchesPage() {
|
export default function HoneyBatchesPage() {
|
||||||
|
const [status, setStatus] = useState<HoneyBatchStatus | "">("");
|
||||||
|
|
||||||
const { data: honeyBatches, isLoading, isError } = useQuery({
|
const { data: honeyBatches, isLoading, isError } = useQuery({
|
||||||
queryKey: ["honey-batches"],
|
queryKey: ["honey-batches", status],
|
||||||
queryFn: fetchHoneyBatches,
|
queryFn: () => fetchHoneyBatches({ status: status || undefined }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -27,6 +49,25 @@ export default function HoneyBatchesPage() {
|
|||||||
<CreateHoneyBatchDialog />
|
<CreateHoneyBatchDialog />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={status || "ALL"}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setStatus(value === "ALL" ? "" : ((value ?? "") as HoneyBatchStatus))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-40">
|
||||||
|
<SelectValue placeholder="Status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="ALL">Alle Status</SelectItem>
|
||||||
|
{STATUS_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-red-600">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function NewInspectionPage() {
|
|||||||
|
|
||||||
const { data: colonies } = useQuery({
|
const { data: colonies } = useQuery({
|
||||||
queryKey: ["colonies"],
|
queryKey: ["colonies"],
|
||||||
queryFn: fetchColonies,
|
queryFn: () => fetchColonies(),
|
||||||
});
|
});
|
||||||
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ export default function AppLayout({ children }: { children: ReactNode }) {
|
|||||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/storage">
|
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/storage">
|
||||||
Lager
|
Lager
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/search">
|
||||||
|
Suche
|
||||||
|
</Link>
|
||||||
{user.role === "ADMIN" ? (
|
{user.role === "ADMIN" ? (
|
||||||
<Link
|
<Link
|
||||||
className="text-sm text-muted-foreground hover:text-foreground"
|
className="text-sm text-muted-foreground hover:text-foreground"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function AssignColonyDialog({ queenId }: { queenId: string }) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [colonyId, setColonyId] = useState("");
|
const [colonyId, setColonyId] = useState("");
|
||||||
const { data: colonies } = useQuery({ queryKey: ["colonies"], queryFn: fetchColonies });
|
const { data: colonies } = useQuery({ queryKey: ["colonies"], queryFn: () => fetchColonies() });
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: () => assignQueenColony(queenId, { colonyId }),
|
mutationFn: () => assignQueenColony(queenId, { colonyId }),
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { fetchQueens } from "@/lib/queens/api";
|
import { fetchQueens } from "@/lib/queens/api";
|
||||||
|
import { QueenStatus } from "@/lib/queens/types";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -14,10 +23,23 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
|
const STATUS_OPTIONS: QueenStatus[] = [
|
||||||
|
"ACTIVE",
|
||||||
|
"RESERVE",
|
||||||
|
"UNMATED",
|
||||||
|
"MATED",
|
||||||
|
"LOST",
|
||||||
|
"REPLACED",
|
||||||
|
"SOLD",
|
||||||
|
"DECEASED",
|
||||||
|
];
|
||||||
|
|
||||||
export default function QueensPage() {
|
export default function QueensPage() {
|
||||||
|
const [status, setStatus] = useState<QueenStatus | "">("");
|
||||||
|
|
||||||
const { data: queens, isLoading, isError } = useQuery({
|
const { data: queens, isLoading, isError } = useQuery({
|
||||||
queryKey: ["queens"],
|
queryKey: ["queens", status],
|
||||||
queryFn: fetchQueens,
|
queryFn: () => fetchQueens({ status: status || undefined }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,6 +51,23 @@ export default function QueensPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={status || "ALL"}
|
||||||
|
onValueChange={(value) => setStatus(value === "ALL" ? "" : ((value ?? "") as QueenStatus))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-40">
|
||||||
|
<SelectValue placeholder="Status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="ALL">Alle Status</SelectItem>
|
||||||
|
{STATUS_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-red-600">
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { search } from "@/lib/search/api";
|
||||||
|
import { SearchResult, SearchResultType } from "@/lib/search/types";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
const TYPE_LABELS: Record<SearchResultType, string> = {
|
||||||
|
Apiary: "Bienenstand",
|
||||||
|
Colony: "Volk",
|
||||||
|
Queen: "Königin",
|
||||||
|
Task: "Aufgabe",
|
||||||
|
HoneyBatch: "Honigcharge",
|
||||||
|
};
|
||||||
|
|
||||||
|
function resultHref(result: SearchResult): string {
|
||||||
|
switch (result.type) {
|
||||||
|
case "Apiary":
|
||||||
|
return `/apiaries/${result.id}`;
|
||||||
|
case "Colony":
|
||||||
|
return `/colonies/${result.id}`;
|
||||||
|
case "Queen":
|
||||||
|
return `/queens/${result.id}`;
|
||||||
|
case "HoneyBatch":
|
||||||
|
return `/honey-batches/${result.id}`;
|
||||||
|
case "Task":
|
||||||
|
return "/tasks";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SearchPage() {
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
|
const { data: results, isLoading } = useQuery({
|
||||||
|
queryKey: ["search", query],
|
||||||
|
queryFn: () => search(query),
|
||||||
|
enabled: query.trim().length > 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||||
|
<h1 className="text-xl font-semibold">Suche</h1>
|
||||||
|
<Input
|
||||||
|
className="h-11 max-w-lg"
|
||||||
|
placeholder="Bienenstand, Volk, Königin, Aufgabe, Charge…"
|
||||||
|
value={query}
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||||
|
{query.trim().length > 0 && !isLoading && (!results || results.length === 0) ? (
|
||||||
|
<p className="text-sm text-muted-foreground">Keine Treffer.</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{results && results.length > 0 ? (
|
||||||
|
<Card className="max-w-lg">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Ergebnisse</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-2">
|
||||||
|
{results.map((result) => (
|
||||||
|
<Link
|
||||||
|
key={`${result.type}-${result.id}`}
|
||||||
|
href={resultHref(result)}
|
||||||
|
className="flex items-center justify-between rounded-md border p-3 text-sm hover:bg-muted"
|
||||||
|
>
|
||||||
|
<span>{result.label}</span>
|
||||||
|
<Badge variant="secondary">{TYPE_LABELS[result.type]}</Badge>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
) : null}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ export default function NewTreatmentPage() {
|
|||||||
|
|
||||||
const { data: colonies } = useQuery({
|
const { data: colonies } = useQuery({
|
||||||
queryKey: ["colonies"],
|
queryKey: ["colonies"],
|
||||||
queryFn: fetchColonies,
|
queryFn: () => fetchColonies(),
|
||||||
});
|
});
|
||||||
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function NewVarroaMeasurementPage() {
|
|||||||
|
|
||||||
const { data: colonies } = useQuery({
|
const { data: colonies } = useQuery({
|
||||||
queryKey: ["colonies"],
|
queryKey: ["colonies"],
|
||||||
queryFn: fetchColonies,
|
queryFn: () => fetchColonies(),
|
||||||
});
|
});
|
||||||
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
const activeColonies = colonies?.filter((colony) => colony.status === "ACTIVE") ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
import { apiFetch } from "@/lib/auth/api-client";
|
import { apiFetch } from "@/lib/auth/api-client";
|
||||||
import { Apiary, ApiaryInput } from "./types";
|
import { Apiary, ApiaryInput, ApiaryStatus } from "./types";
|
||||||
|
|
||||||
export function fetchApiaries(): Promise<Apiary[]> {
|
export interface ApiaryFilter {
|
||||||
return apiFetch<Apiary[]>("/apiaries");
|
status?: ApiaryStatus;
|
||||||
|
location?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchApiaries(filter: ApiaryFilter = {}): Promise<Apiary[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filter.status) params.set("status", filter.status);
|
||||||
|
if (filter.location) params.set("location", filter.location);
|
||||||
|
const query = params.toString();
|
||||||
|
return apiFetch<Apiary[]>(`/apiaries${query ? `?${query}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchApiary(id: string): Promise<Apiary> {
|
export function fetchApiary(id: string): Promise<Apiary> {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { apiFetch } from "@/lib/auth/api-client";
|
|||||||
import {
|
import {
|
||||||
Colony,
|
Colony,
|
||||||
ColonyHistory,
|
ColonyHistory,
|
||||||
|
ColonyStatus,
|
||||||
CreateColonyInput,
|
CreateColonyInput,
|
||||||
MergeColonyInput,
|
MergeColonyInput,
|
||||||
MoveColonyInput,
|
MoveColonyInput,
|
||||||
@@ -9,8 +10,17 @@ import {
|
|||||||
UpdateColonyInput,
|
UpdateColonyInput,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export function fetchColonies(): Promise<Colony[]> {
|
export interface ColonyFilter {
|
||||||
return apiFetch<Colony[]>("/colonies");
|
status?: ColonyStatus;
|
||||||
|
apiaryId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchColonies(filter: ColonyFilter = {}): Promise<Colony[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filter.status) params.set("status", filter.status);
|
||||||
|
if (filter.apiaryId) params.set("apiaryId", filter.apiaryId);
|
||||||
|
const query = params.toString();
|
||||||
|
return apiFetch<Colony[]>(`/colonies${query ? `?${query}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchColony(id: string): Promise<Colony> {
|
export function fetchColony(id: string): Promise<Colony> {
|
||||||
|
|||||||
@@ -3,11 +3,19 @@ import {
|
|||||||
CreateHoneyBatchInput,
|
CreateHoneyBatchInput,
|
||||||
CreateHoneyMeasurementInput,
|
CreateHoneyMeasurementInput,
|
||||||
HoneyBatch,
|
HoneyBatch,
|
||||||
|
HoneyBatchStatus,
|
||||||
Traceability,
|
Traceability,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export function fetchHoneyBatches(): Promise<HoneyBatch[]> {
|
export interface HoneyBatchFilter {
|
||||||
return apiFetch<HoneyBatch[]>("/honey-batches");
|
status?: HoneyBatchStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchHoneyBatches(filter: HoneyBatchFilter = {}): Promise<HoneyBatch[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filter.status) params.set("status", filter.status);
|
||||||
|
const query = params.toString();
|
||||||
|
return apiFetch<HoneyBatch[]>(`/honey-batches${query ? `?${query}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchHoneyBatch(id: string): Promise<HoneyBatch> {
|
export function fetchHoneyBatch(id: string): Promise<HoneyBatch> {
|
||||||
|
|||||||
@@ -4,11 +4,19 @@ import {
|
|||||||
CreateQueenInput,
|
CreateQueenInput,
|
||||||
Queen,
|
Queen,
|
||||||
QueenColonyHistoryEntry,
|
QueenColonyHistoryEntry,
|
||||||
|
QueenStatus,
|
||||||
UpdateQueenInput,
|
UpdateQueenInput,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export function fetchQueens(): Promise<Queen[]> {
|
export interface QueenFilter {
|
||||||
return apiFetch<Queen[]>("/queens");
|
status?: QueenStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchQueens(filter: QueenFilter = {}): Promise<Queen[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filter.status) params.set("status", filter.status);
|
||||||
|
const query = params.toString();
|
||||||
|
return apiFetch<Queen[]>(`/queens${query ? `?${query}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchQueen(id: string): Promise<Queen> {
|
export function fetchQueen(id: string): Promise<Queen> {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { apiFetch } from "@/lib/auth/api-client";
|
||||||
|
import { SearchResult } from "./types";
|
||||||
|
|
||||||
|
export function search(q: string): Promise<SearchResult[]> {
|
||||||
|
return apiFetch<SearchResult[]>(`/search?q=${encodeURIComponent(q)}`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export type SearchResultType = "Apiary" | "Colony" | "Queen" | "Task" | "HoneyBatch";
|
||||||
|
|
||||||
|
export interface SearchResult {
|
||||||
|
type: SearchResultType;
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
@@ -122,7 +122,7 @@ Abgeleitet aus [`docs/lastenheft.md`](docs/lastenheft.md) Abschnitt 26. Reihenfo
|
|||||||
- Backend: `SearchModule` (`GET /api/v1/search?q=`), Filter-Query-Parameter in bestehenden List-Endpunkten (Standort, Status, Zeitraum etc., kombinierbar)
|
- Backend: `SearchModule` (`GET /api/v1/search?q=`), Filter-Query-Parameter in bestehenden List-Endpunkten (Standort, Status, Zeitraum etc., kombinierbar)
|
||||||
- Frontend: `/search`, Filterleisten in allen Listen-Views
|
- Frontend: `/search`, Filterleisten in allen Listen-Views
|
||||||
- [x] Backend-Modul fertig, getestet, grün, committet
|
- [x] Backend-Modul fertig, getestet, grün, committet
|
||||||
- [ ] Frontend-Views fertig, getestet, grün, committet
|
- [x] Frontend-Views fertig, getestet, grün, committet
|
||||||
|
|
||||||
### 15. Responsive Smartphone-Oberfläche
|
### 15. Responsive Smartphone-Oberfläche
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user