round13: manuelle vs. echte Aenderungen trennen, Scan-Uebersicht ueber Navigation persistieren, ausgeblendete Dienste als Tab, Suche ueber Geraetenamen

This commit is contained in:
2026-07-23 01:21:31 +02:00
parent 1f8218dff1
commit 2772c29036
10 changed files with 322 additions and 126 deletions

View File

@@ -426,6 +426,7 @@ export function ServicesPage() {
const [localOrder, setLocalOrder] = useState<Service[] | null>(null);
const [sortColumn, setSortColumn] = useState<SortColumn>(null);
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
const [activeTab, setActiveTab] = useState<"visible" | "hidden">("visible");
const macByDeviceId = useMemo(() => {
const map: Record<string, string | null> = {};
@@ -456,10 +457,15 @@ export function ServicesPage() {
const baseList = localOrder ?? services ?? [];
const hiddenCount = services?.filter((s) => !s.visible).length ?? 0;
const visibleCount = services ? services.length - hiddenCount : 0;
const tabFilteredBase = useMemo(
() => baseList.filter((s) => (activeTab === "visible" ? s.visible : !s.visible)),
[baseList, activeTab]
);
const list = useMemo(() => {
if (!sortColumn) return baseList;
const sorted = [...baseList].sort((a, b) => {
if (!sortColumn) return tabFilteredBase;
const sorted = [...tabFilteredBase].sort((a, b) => {
let cmp = 0;
switch (sortColumn) {
case "displayName":
@@ -491,7 +497,7 @@ export function ServicesPage() {
return sortDirection === "asc" ? cmp : -cmp;
});
return sorted;
}, [baseList, sortColumn, sortDirection, ipByDeviceId]);
}, [tabFilteredBase, sortColumn, sortDirection, ipByDeviceId]);
function handleHeaderClick(column: SortColumn) {
if (sortColumn === column) {
@@ -539,13 +545,32 @@ export function ServicesPage() {
<div>
<AdminPageHeader
title="Dienste"
description={
hiddenCount > 0
? `Spaltenköpfe anklickbar zum Sortieren; Drag & Drop (⠿⠿) nur in der Standard-Reihenfolge. Name, Kategorie, Alias, IP/Port/Protokoll und Reihenfolge bleiben bei erneuten Scans erhalten. ${hiddenCount} Dienst(e) sind aktuell in der Suche ausgeblendet (🙈).`
: "Spaltenköpfe anklickbar zum Sortieren; Drag & Drop (⠿⠿) nur in der Standard-Reihenfolge. Name, Kategorie, Alias, IP/Port/Protokoll und Reihenfolge bleiben bei erneuten Scans erhalten."
}
description="Spaltenköpfe anklickbar zum Sortieren; Drag & Drop (⠿⠿) nur in der Standard-Reihenfolge. Name, Kategorie, Alias, IP/Port/Protokoll und Reihenfolge bleiben bei erneuten Scans erhalten."
/>
<div className="mb-4 flex gap-1 border-b border-black/10 dark:border-white/10">
<button
onClick={() => setActiveTab("visible")}
className={`px-3 py-2 text-sm font-medium ${
activeTab === "visible"
? "border-b-2 border-black text-black dark:border-white dark:text-white"
: "text-black/40 hover:text-black/70 dark:text-white/40 dark:hover:text-white/70"
}`}
>
Sichtbar ({visibleCount})
</button>
<button
onClick={() => setActiveTab("hidden")}
className={`px-3 py-2 text-sm font-medium ${
activeTab === "hidden"
? "border-b-2 border-black text-black dark:border-white dark:text-white"
: "text-black/40 hover:text-black/70 dark:text-white/40 dark:hover:text-white/70"
}`}
>
🙈 Ausgeblendet ({hiddenCount})
</button>
</div>
{sortColumn ? (
<div className="mb-3">
<Button size="sm" variant="ghost" onClick={() => setSortColumn(null)}>
@@ -600,8 +625,9 @@ export function ServicesPage() {
</div>
) : (
<p className="text-sm text-black/40 dark:text-white/40">
Noch keine Dienste vorhanden. Scanne ein Gerät unter Geräte, um automatisch welche
zu finden, oder importiere eine Liste oben.
{activeTab === "hidden"
? "Keine ausgeblendeten Dienste."
: "Noch keine Dienste vorhanden. Scanne ein Gerät unter „Geräte“, um automatisch welche zu finden, oder importiere eine Liste oben."}
</p>
)}
</div>