diff --git a/admin-compliance/app/api/sdk/v1/canonical/route.ts b/admin-compliance/app/api/sdk/v1/canonical/route.ts index 3076e13..5b1b608 100644 --- a/admin-compliance/app/api/sdk/v1/canonical/route.ts +++ b/admin-compliance/app/api/sdk/v1/canonical/route.ts @@ -27,7 +27,7 @@ export async function GET(request: NextRequest) { case 'controls': { const controlParams = new URLSearchParams() const passthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category', - 'target_audience', 'source', 'search', 'control_type', 'sort', 'order', 'limit', 'offset'] + 'target_audience', 'source', 'search', 'control_type', 'exclude_duplicates', 'sort', 'order', 'limit', 'offset'] for (const key of passthrough) { const val = searchParams.get(key) if (val) controlParams.set(key, val) @@ -40,7 +40,7 @@ export async function GET(request: NextRequest) { case 'controls-count': { const countParams = new URLSearchParams() const countPassthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category', - 'target_audience', 'source', 'search', 'control_type'] + 'target_audience', 'source', 'search', 'control_type', 'exclude_duplicates'] for (const key of countPassthrough) { const val = searchParams.get(key) if (val) countParams.set(key, val) diff --git a/admin-compliance/app/sdk/control-library/page.tsx b/admin-compliance/app/sdk/control-library/page.tsx index 0ea5bb4..07549c9 100644 --- a/admin-compliance/app/sdk/control-library/page.tsx +++ b/admin-compliance/app/sdk/control-library/page.tsx @@ -54,6 +54,7 @@ export default function ControlLibraryPage() { const [audienceFilter, setAudienceFilter] = useState('') const [sourceFilter, setSourceFilter] = useState('') const [typeFilter, setTypeFilter] = useState('') + const [hideDuplicates, setHideDuplicates] = useState(true) const [sortBy, setSortBy] = useState<'id' | 'newest' | 'oldest' | 'source'>('id') // CRUD state @@ -96,10 +97,11 @@ export default function ControlLibraryPage() { if (audienceFilter) p.set('target_audience', audienceFilter) if (sourceFilter) p.set('source', sourceFilter) if (typeFilter) p.set('control_type', typeFilter) + if (hideDuplicates) p.set('exclude_duplicates', 'true') if (debouncedSearch) p.set('search', debouncedSearch) if (extra) for (const [k, v] of Object.entries(extra)) p.set(k, v) return p.toString() - }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, audienceFilter, sourceFilter, typeFilter, debouncedSearch]) + }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, audienceFilter, sourceFilter, typeFilter, hideDuplicates, debouncedSearch]) // Load metadata (domains, sources — once + on refresh) const loadMeta = useCallback(async () => { @@ -167,7 +169,7 @@ export default function ControlLibraryPage() { useEffect(() => { loadControls() }, [loadControls]) // Reset page when filters change - useEffect(() => { setCurrentPage(1) }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, audienceFilter, sourceFilter, typeFilter, debouncedSearch, sortBy]) + useEffect(() => { setCurrentPage(1) }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, audienceFilter, sourceFilter, typeFilter, hideDuplicates, debouncedSearch, sortBy]) // Pagination const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)) @@ -623,6 +625,15 @@ export default function ControlLibraryPage() { +