feat(PROJ-9): implement labels frontend - LabelList, LabelPicker, search integration, admin UI

This commit is contained in:
sysops
2026-03-18 09:51:10 +01:00
parent 2e9f1f0471
commit cee75094ad
7 changed files with 923 additions and 1 deletions
+24 -1
View File
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import { useAuth } from "@/hooks/useAuth";
import { searchEmails, exportMailsZIP, uploadMailFilesUser, getUploadProgressUser, type SearchHit, type UploadJob } from "@/lib/api";
import { Navbar } from "@/components/navbar";
import { LabelList } from "@/components/LabelList";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent } from "@/components/ui/card";
@@ -56,6 +57,7 @@ export default function SearchPage() {
const [dateTo, setDateTo] = useState("");
const [sort, setSort] = useState("date_desc");
const [hasAttachment, setHasAttachment] = useState<boolean | undefined>(undefined);
const [selectedLabelId, setSelectedLabelId] = useState<number | null>(null);
const [results, setResults] = useState<SearchHit[]>([]);
const [total, setTotal] = useState(0);
@@ -94,6 +96,7 @@ export default function SearchPage() {
date_to: dateTo || undefined,
sort: sort !== "date_desc" ? sort : undefined,
has_attachment: hasAttachment,
label_id: selectedLabelId ?? undefined,
page: p,
page_size: PAGE_SIZE,
});
@@ -108,7 +111,7 @@ export default function SearchPage() {
setSearching(false);
}
},
[query, fromFilter, toFilter, dateFrom, dateTo, sort, hasAttachment]
[query, fromFilter, toFilter, dateFrom, dateTo, sort, hasAttachment, selectedLabelId]
);
// Alle Mails beim Öffnen der Seite laden — direkt, ohne useCallback-Closure
@@ -129,6 +132,13 @@ export default function SearchPage() {
.finally(() => setSearching(false));
}, [user]);
// Re-search when label selection changes
useEffect(() => {
if (!user || !searched) return;
doSearch(1);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedLabelId]);
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
doSearch(1);
@@ -216,6 +226,17 @@ export default function SearchPage() {
</div>
)}
{!authLoading && user && (<>
<div className="flex gap-6">
{/* Label sidebar */}
<div className="hidden md:block w-48 shrink-0">
<LabelList
selectedLabelId={selectedLabelId}
onLabelSelect={setSelectedLabelId}
/>
</div>
{/* Main content */}
<div className="flex-1 min-w-0">
<form onSubmit={handleSubmit} className="space-y-4">
<div className="flex gap-2">
<Input
@@ -439,6 +460,8 @@ export default function SearchPage() {
</>
) : null}
</div>
</div>{/* end flex-1 */}
</div>{/* end flex gap-6 */}
<Dialog open={exportOpen} onOpenChange={setExportOpen}>
<DialogContent>