diff --git a/README.md b/README.md index 86826ca..e6e6d2c 100644 --- a/README.md +++ b/README.md @@ -618,13 +618,18 @@ Dazu kommen - ein **Suchfeld** über Name, Ziel, Kurz-URL und Typ, - eine **Sortierung** nach Datum, Name oder Scan-Anzahl, -- ein **Gruppenfilter**, sobald Codes in mehr als einer Gruppe liegen. +- ein **Gruppenfilter**, sobald Codes in mehr als einer Gruppe liegen, +- ein **Filter nach Art**, sobald dynamische und statische Codes + nebeneinander liegen. Auf schmalen Bildschirmen blendet die Liste die hinteren Spalten aus; die Kurz-URL rückt dann unter den Namen. Zum Herunterladen dient dort die Kartenansicht. -Statische Codes erscheinen hier nicht, weil sie nicht gespeichert werden. +Abgelegte **statische Codes** stehen mit in der Liste, tragen aber ein +Abzeichen „statisch“ statt einer Kurz-URL und zählen keine Scans. Steht der +Filter auf „Nur statische“, fällt die Scan-Spalte weg und die Sortierung nach +Scans mit ihr — bei einer Liste aus lauter Nullen sagt sie nichts. Bei einem **Feedback-Code** führt aus der Zeile ein Stern-Symbol zur Auswertung: Durchschnitt, Verteilung der Noten und die abgegebenen Kommentare. diff --git a/src/app/(app)/meine-codes/page.tsx b/src/app/(app)/meine-codes/page.tsx index 26bcf7d..12357a9 100644 --- a/src/app/(app)/meine-codes/page.tsx +++ b/src/app/(app)/meine-codes/page.tsx @@ -27,9 +27,9 @@ export default async function MyCodesPage() { Meine Codes

- Alle dynamischen QR-Codes mit ihrem aktuellen Ziel und der Anzahl der - Scans. Statische Codes erscheinen hier nicht — sie werden nicht - gespeichert. + Dynamische Codes mit ihrem aktuellen Ziel und der Anzahl der Scans, + dazu abgelegte statische Codes. Über die Auswahl „Nach Art filtern“ + lässt sich die Liste auf eine der beiden Arten einschränken.

diff --git a/src/components/codes-list.tsx b/src/components/codes-list.tsx index 5ab8b16..b7e94b9 100644 --- a/src/components/codes-list.tsx +++ b/src/components/codes-list.tsx @@ -43,6 +43,8 @@ function formatShortDate(value: string | null): string { type View = "list" | "cards"; type SortKey = "neueste" | "aelteste" | "name" | "scans"; +/** Betriebsart, auf die die Liste eingeschränkt ist. */ +type ModeFilter = "alle" | "dynamic" | "static"; const SORT_LABELS: Record = { neueste: "Neueste zuerst", @@ -51,6 +53,12 @@ const SORT_LABELS: Record = { scans: "Meiste Scans", }; +const MODE_LABELS: Record = { + alle: "Alle Arten", + dynamic: "Nur dynamische", + static: "Nur statische", +}; + /** Merkt sich die gewählte Ansicht über Seitenwechsel hinweg. */ const VIEW_STORAGE_KEY = "qrgen.codes.view"; @@ -94,6 +102,7 @@ export function CodesList({ const [query, setQuery] = useState(""); const [sort, setSort] = useState("neueste"); const [groupFilter, setGroupFilter] = useState("alle"); + const [modeFilter, setModeFilter] = useState("alle"); // Erst nach dem Einhängen lesen — auf dem Server gibt es keinen // localStorage, und ein abweichender Startwert führte zu Hydrationsfehlern. @@ -107,6 +116,14 @@ export function CodesList({ window.localStorage.setItem(VIEW_STORAGE_KEY, next); }; + const changeModeFilter = (next: ModeFilter) => { + setModeFilter(next); + // Statische Codes zählen keine Scans. Bleibt die Sortierung danach + // stehen, sortiert die Liste nach lauter Nullen — und die Auswahl bietet + // eine Reihenfolge an, die es dort gar nicht gibt. + if (next === "static" && sort === "scans") setSort("neueste"); + }; + const load = useCallback(async () => { try { const result = await apiJson<{ codes: QrCodeDto[] }>("/api/codes"); @@ -241,11 +258,25 @@ export function CodesList({ ); }, [codes]); + // Welche Betriebsarten überhaupt vorkommen. Ein Filter, der nichts zu + // filtern hat, steht nur im Weg — deshalb erscheint er erst, wenn beide + // Arten in der Liste stehen. + const kinds = useMemo(() => { + let dynamisch = false; + let statisch = false; + for (const code of codes ?? []) { + if (code.mode === "static") statisch = true; + else dynamisch = true; + } + return { dynamisch, statisch, beide: dynamisch && statisch }; + }, [codes]); + const visible = useMemo(() => { const needle = query.trim().toLowerCase(); const filtered = (codes ?? []).filter((code) => { if (groupFilter !== "alle" && code.groupId !== groupFilter) return false; + if (modeFilter !== "alle" && code.mode !== modeFilter) return false; if (!needle) return true; return [ displayName(code), @@ -273,7 +304,7 @@ export function CodesList({ } }); return sorted; - }, [codes, query, sort, groupFilter]); + }, [codes, query, sort, groupFilter, modeFilter]); /* ---------------------------------------------------------------- View */ @@ -287,6 +318,10 @@ export function CodesList({ const svgHref = (code: QrCodeDto) => `/api/codes/${code.id}/qr?format=svg&download=1`; + // Zeigt die Liste ausschließlich statische Codes, hat die Scan-Spalte + // nichts zu sagen — dann fällt sie weg, samt der Sortierung danach. + const nurStatische = modeFilter === "static" || (kinds.statisch && !kinds.dynamisch); + const toolbar = codes.length > 0 && (
@@ -320,17 +355,36 @@ export function CodesList({ ) : null} + {kinds.beide ? ( + + ) : null} +
0 && visible.length === 0 ? (

- Kein Code passt zur Suche.{" "} + Kein Code passt zu Suche und Filtern.{" "}