Files
member-console/internal/embeds/templates/partials/operator_grants.html
T
cgalo5758 257955c9d3 Add operator list-scale contract and People directory
Governed operator lists (organizations, grants, people, billing×4) gain
server-side search, status filters, and 50-row pages with true totals
from count(*) OVER(); state is URL-addressable, out-of-range pages
clamp,
and no-match is distinct from true-empty.

People is the eighth flat sidebar entry: /operator/persons lists persons
newest-joined first (excluding the reserved system person), rows linking
to the existing detail.

Billing gains an operator invoice detail at
/operator/billing/invoices/{invoiceID} reusing the member projection;
open invoices past due present as Overdue (derived, filterable, stored
status untouched); all four views lead with the linked organization and
mute object IDs.

Grants filter over the derived Live/Superseded/Inactive state, the SQL
HAVING predicate pinned to the Go derivation by test. Embedded lists
(org composite ledger, Tier changes) adopt the shared controls under
namespaced params with sibling-state-preserving URLs and scoped htmx
swaps that hold the viewport.

Review corrections: blocked ladder Delete renders disabled with tooltip
and mutations fire toasts; collapse triggers paint their open state;
sections use outside headings; plan topology drops the orphan-product
check; domains policy collapses behind a disclosure.
2026-08-24 03:58:18 -05:00

91 lines
3.8 KiB
HTML

<!-- Grants management partial for operator — READ-ONLY.
Per operator-mpa-conversion D8, Issue/Extend/Revoke and CreateGrant
are exposed only on /operator/organizations/{orgID}. This view is
browse/audit only.
Delivery renders the derived state from the shared delivery-state
derivation (ux-honest-surfaces design decision 1; the paged query
ListGrantsWithDeliveryPage repeats the identical CASE expression,
design D4) — the same vocabulary and badge treatment the org-detail
composite's Grants table uses, so the two surfaces can never disagree.
Ledger (grants.status) is issuance history and is deliberately NOT a
colored state badge — see the explanation below.
Governed by operator-list-scale: server-side search, the delivery-state
filter, and true-total pagination (operator_list_controls.html's
listControls/listPager/listNoMatch, driven by .Nav). -->
<h1 class="h2 mb-3">Grants</h1>
{{ if .Error }}
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> {{ .Error }}
</div>
{{ else }}
{{ template "listControls" .Nav }}
{{ if eq (len .Grants) 0 }}
{{ if .Nav.Filtered }}
{{ template "listNoMatch" .Nav }}
{{ else }}
<div class="text-center py-4">
<p class="text-muted mb-2">No grants found.</p>
{{ if eq (len .Products) 0 }}
<p class="text-muted small mb-0">A grant delivers a published product to an organization, but no product has been published yet. <a href="/operator/products">Go to Products</a> to publish one.</p>
{{ else }}
<p class="text-muted small mb-0">Grants are issued from each organization's detail page, not from here.</p>
{{ end }}
</div>
{{ end }}
{{ else }}
<p class="text-muted small mb-2">
Grants are the issuance ledger, not the delivery record: Ledger is when
and why a grant was recorded (and whether it was later revoked or
expired). Delivery is derived by joining each grant to its pool
provision — at most one grant per ladder position is
<span class="badge text-bg-success">Live</span> at a time; the rest are
history.
</p>
<div class="table-responsive">
<table class="table table-hover table-sm">
<thead>
<tr>
<th>Organization</th>
<th>Grant Target</th>
<th>Qty</th>
<th>Reason</th>
<th>Ledger</th>
<th>Delivery</th>
<th>Created</th>
</tr>
</thead>
<tbody>
{{ range .Grants }}
<tr{{ if ne .DeliveryState "live" }} class="text-muted"{{ end }}>
<td>{{ .OrgName }}</td>
<td>{{ if .ProductName }}{{ .ProductName }} <span class="badge bg-secondary">product</span>{{ else }}{{ .EntitlementSetName }} <span class="badge text-bg-info">set</span>{{ end }}</td>
<td>{{ .Quantity }}</td>
<td>{{ .GrantReason }}</td>
<td><small>{{ .Status }}</small></td>
<td>
{{ if eq .DeliveryState "live" }}
<span class="badge text-bg-success">Live</span>
{{ if .ExtendsLabel }}<br><small class="text-muted">extends {{ .ExtendsLabel }}</small>{{ end }}
{{ else if eq .DeliveryState "superseded" }}
<span class="badge text-bg-secondary">Superseded</span>
{{ if .ReplacedByLabel }}<br><small class="text-muted">replaced by {{ .ReplacedByLabel }}</small>{{ end }}
{{ else }}
<span class="badge text-bg-light border">Inactive</span>
{{ end }}
</td>
<td>{{ .CreatedAt }}</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ end }}
{{ template "listPager" .Nav }}
{{ end }}