feat(iace): add withdrawn filter to norms library frontend

- Add withdrawn/valid_until/replaced_by to Norm interface
- Add Status filter (Aktiv/Zurueckgezogen) — defaults to "Aktiv"
- Withdrawn norms hidden by default, viewable via filter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-09 08:50:26 +02:00
parent 4bfb438c92
commit 4d708b4443
@@ -15,6 +15,9 @@ export interface Norm {
mandatory: boolean
relevant_sections: string[]
beuth_url: string
withdrawn?: boolean
valid_until?: string
replaced_by?: string
}
const PER_PAGE = 50
@@ -30,6 +33,11 @@ const MANDATORY_OPTIONS = [
{ value: 'ja', label: 'Pflicht: Ja' },
{ value: 'nein', label: 'Pflicht: Nein' },
]
const STATUS_OPTIONS = [
{ value: '', label: 'Status: Alle' },
{ value: 'active', label: 'Aktiv' },
{ value: 'withdrawn', label: 'Zurueckgezogen' },
]
interface Props { norms: Norm[] }
@@ -38,6 +46,7 @@ export default function NormenTab({ norms }: Props) {
const [debounced, setDebounced] = useState('')
const [typeFilter, setTypeFilter] = useState('')
const [mandatoryFilter, setMandatoryFilter] = useState('')
const [statusFilter, setStatusFilter] = useState('active')
const [page, setPage] = useState(1)
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
@@ -46,7 +55,7 @@ export default function NormenTab({ norms }: Props) {
return () => { if (timer.current) clearTimeout(timer.current) }
}, [search])
useEffect(() => { setPage(1) }, [debounced, typeFilter, mandatoryFilter])
useEffect(() => { setPage(1) }, [debounced, typeFilter, mandatoryFilter, statusFilter])
const filtered = useMemo(() => {
const q = debounced.toLowerCase()
@@ -55,9 +64,11 @@ export default function NormenTab({ norms }: Props) {
if (typeFilter && n.norm_type !== typeFilter) return false
if (mandatoryFilter === 'ja' && !n.mandatory) return false
if (mandatoryFilter === 'nein' && n.mandatory) return false
if (statusFilter === 'active' && n.withdrawn) return false
if (statusFilter === 'withdrawn' && !n.withdrawn) return false
return true
})
}, [norms, debounced, typeFilter, mandatoryFilter])
}, [norms, debounced, typeFilter, mandatoryFilter, statusFilter])
const totalPages = Math.ceil(filtered.length / PER_PAGE)
const pageItems = filtered.slice((page - 1) * PER_PAGE, page * PER_PAGE)
@@ -70,6 +81,7 @@ export default function NormenTab({ norms }: Props) {
</div>
<FilterDropdown label="Normtyp" value={typeFilter} options={TYPE_OPTIONS} onChange={setTypeFilter} />
<FilterDropdown label="Pflicht" value={mandatoryFilter} options={MANDATORY_OPTIONS} onChange={setMandatoryFilter} />
<FilterDropdown label="Status" value={statusFilter} options={STATUS_OPTIONS} onChange={setStatusFilter} />
<span className="text-sm text-gray-500 dark:text-gray-400 ml-auto">
{norms.length} Normen{filtered.length !== norms.length && ` (${filtered.length} gefiltert)`}
</span>