{{- /* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial */ -}} {{- /* SPDX-FileCopyrightText: 2025-2026 Christian Galo */ -}} {{/* operator_products.html Products list (catalog hub) body partial for GET /operator/products, dispatched by GetProductsPage. Built from the page-anatomy parts: the header carries the true total (through the pager); the sibling catalog surfaces (Entitlement sets, Plan ladders) ride a toolbar row below it, the billing pills' pattern, because the header's one slot cannot hold two nav actions; rows navigate through the product name (chrome-conventions "Row navigation is the primary column's link"). Catalog overview stays a compact summary of the WHOLE catalog's shape (independent of the page and facet on screen), not a copy of the child surfaces (ux-ia-naming 2.1). Governed by operator-list-scale below the list section header: search by name, the lifecycle-status facet, true-total pagination (product-management "The list takes the scaffold", design D3). The Status cell is the purchasability verdict, not the raw lifecycle badge, except for draft/retired products where the lifecycle word itself is the state (design D1). Three states inside the list section: - Zero rows AND Nav.Filtered: the search or facet matched nothing; listNoMatch, distinct from true-empty (empty-state-guidance). - Zero rows AND not Nav.Filtered: no products exist yet; emptyState (EntitlementSetGate). - Otherwise: listControls, the table, listPager. */}} {{ template "pageHeader" .Header }} {{/* The catalog navigates itself the way Billing does: one rail entry (Products), a pill row for the sibling catalog surfaces (maintainer, 2026-08-31: the outline buttons read as actions; pills are the established in-section navigation vocabulary). Entitlement sets and Plan ladders adopt the same row in their own sweep groups. */}} {{ template "catalogPills" "products" }} {{ if .Error }} {{ end }} {{ template "sectionHeader" .CatalogOverviewHeader }}
{{/* The six counts render through the readout part, the same value and the same size as the landing surface's tiles and the Stripe delivery queue (overview-consistency D3, round 2). */}}
{{ template "readout" .Summary.TotalReadout }}
{{ template "readout" .Summary.PublishedReadout }}
{{ template "readout" .Summary.DraftReadout }}
{{ template "readout" .Summary.RetiredReadout }}
{{ template "readout" .Summary.PublishedTiersReadout }}
{{ template "readout" .Summary.PublishedOffLadderReadout }}
{{/* design D20 (round 4) "record creation gets its own page": the list is the page's one section; its header's "New product" action is the page's one filled call to action, a link to /operator/products/new. */}} {{ template "sectionHeader" .ListHeader }} {{ if eq (len .Products) 0 }} {{ if .Nav.Filtered }} {{ template "listControls" .Nav }} {{ template "listNoMatch" .Nav }} {{ else }} {{ template "emptyState" .EntitlementSetGate }} {{ end }} {{ else }} {{ template "listControls" .Nav }}
{{ range .Products }} {{/* ACC-32: this table already uses "—" to mean "no value set" (Display category, Entitlement set); a "No" here would read as the same not-applicable marker even though Visibility is always a definite state, so the cell states which one instead of Yes/No. */}} {{ end }}
Name Display category Entitlement set Status Visibility Created
{{ .Name }} {{ if .DisplayCategory }}{{ template "statusBadge" .CategoryBadge }}{{ else }}{{ end }} {{ if .EntitlementSetName }}{{ .EntitlementSetName }}{{ else }}{{ end }} {{ template "statusBadge" .StatusBadge }}{{ if .StatusNote }} {{ .StatusNote }}{{ end }}{{ if .IsPublic }}Listed{{ else }}Unlisted{{ end }} {{ .CreatedAt }}
{{ template "listPager" .Nav }} {{ end }}