From bc96eed0409f4ddc4455f395cb1fd0e855e6f186 Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Date: Fri, 17 Jul 2026 08:07:09 +0100
Subject: [PATCH] Portal Policies: table layout with colour-matched icons
(#6994)
# Description of Changes
Reworks the Policies catalogue from blocky cards into a clean table with
tinted category icons and tone-matched "enforces" chips, so it matches
the styling used elsewhere in the portal.
Part of a portal (processor) UI-consistency pass, split into small
focused PRs.
## Before / after
---
## Checklist
### General
- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings
### Documentation
- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
### Translations (if applicable)
- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)
### UI Changes (if applicable)
- [x] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
### Testing (if applicable)
- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
---
.../public/locales/en-US/translation.toml | 7 +
.../src/portal/components/PolicySummary.tsx | 6 +-
.../policies/PolicyCatalogueTable.tsx | 138 ++++++++++++++++++
.../policies/PolicyCategoryIcon.css | 15 ++
.../policies/PolicyCategoryIcon.tsx | 16 ++
.../components/policies/PolicySetupWizard.tsx | 6 +-
frontend/editor/src/portal/views/Policies.css | 35 +++++
frontend/editor/src/portal/views/Policies.tsx | 29 ++--
8 files changed, 227 insertions(+), 25 deletions(-)
create mode 100644 frontend/editor/src/portal/components/policies/PolicyCatalogueTable.tsx
create mode 100644 frontend/editor/src/portal/components/policies/PolicyCategoryIcon.css
create mode 100644 frontend/editor/src/portal/components/policies/PolicyCategoryIcon.tsx
diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml
index 4bb5ae928b..eada1744c4 100644
--- a/frontend/editor/public/locales/en-US/translation.toml
+++ b/frontend/editor/public/locales/en-US/translation.toml
@@ -7515,6 +7515,13 @@ label = "Docs enforced"
description = "Configured but not firing"
label = "Paused"
+[portal.policies.table]
+appliesTo = "Applies to"
+docs = "Docs enforced"
+enforces = "Enforces"
+policy = "Policy"
+status = "Status"
+
[portal.policies.wizard.actions]
back = "Back"
cancel = "Cancel"
diff --git a/frontend/editor/src/portal/components/PolicySummary.tsx b/frontend/editor/src/portal/components/PolicySummary.tsx
index 9be6ff49cf..9979783536 100644
--- a/frontend/editor/src/portal/components/PolicySummary.tsx
+++ b/frontend/editor/src/portal/components/PolicySummary.tsx
@@ -15,7 +15,7 @@ import {
type CatalogueEntry,
type PoliciesResponse,
} from "@portal/api/policies";
-import { policyCategoryIcon } from "@app/components/policies/policyCategoryIcon";
+import { PolicyCategoryBadge } from "@portal/components/policies/PolicyCategoryIcon";
import "@portal/components/PolicySummary.css";
/**
@@ -63,9 +63,7 @@ export function PolicySummary() {
header: t("portal.policySummary.column.policy"),
render: ({ entry }) => (
-
- {policyCategoryIcon(entry.category.id)}
-
+
{t(entry.category.label)}{t(entry.category.desc)}
diff --git a/frontend/editor/src/portal/components/policies/PolicyCatalogueTable.tsx b/frontend/editor/src/portal/components/policies/PolicyCatalogueTable.tsx
new file mode 100644
index 0000000000..6986f66f74
--- /dev/null
+++ b/frontend/editor/src/portal/components/policies/PolicyCatalogueTable.tsx
@@ -0,0 +1,138 @@
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { Button, Chip, StatusBadge, Table, type TableColumn } from "@app/ui";
+import type { CatalogueEntry } from "@portal/api/policies";
+import { PolicyCategoryBadge } from "@portal/components/policies/PolicyCategoryIcon";
+import "@portal/views/Policies.css";
+
+interface PolicyCatalogueTableProps {
+ entries: CatalogueEntry[];
+ onOpen: (entry: CatalogueEntry) => void;
+ /** Setup is unavailable (e.g. the AI engine is off): shown, but not openable. */
+ isLocked?: (entry: CatalogueEntry) => boolean;
+ /** Chip text explaining why setup is locked (e.g. "Requires AI engine"). */
+ lockedLabel?: string;
+}
+
+/**
+ * The policy catalogue as a proper data table (Policy / Enforces / Applies to /
+ * Docs / Status), replacing the stacked full-width cards that read as "blocky".
+ * Same shared Table + StatusBadge + Chip primitives the Sources, Documents and
+ * Home policy tables use, so every list page in the portal now reads alike.
+ */
+export function PolicyCatalogueTable({
+ entries,
+ onOpen,
+ isLocked,
+ lockedLabel,
+}: PolicyCatalogueTableProps) {
+ const { t } = useTranslation();
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ key: "policy",
+ header: t("portal.policies.table.policy", "Policy"),
+ render: (entry) => (
+