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.
84 lines
3.7 KiB
HTML
84 lines
3.7 KiB
HTML
<!-- Subscriptions partial for operator -->
|
|
{{ if .Error }}
|
|
<div class="alert alert-danger" role="alert">
|
|
<strong>Error:</strong> {{ .Error }}
|
|
</div>
|
|
{{ else }}
|
|
{{ template "listControls" .Nav }}
|
|
{{ if eq (len .Subscriptions) 0 }}
|
|
{{ if .Nav.Filtered }}
|
|
{{ template "listNoMatch" .Nav }}
|
|
{{ else }}
|
|
<div class="text-center py-4">
|
|
{{ if not .StripeConfigured }}
|
|
<p class="text-muted mb-0">Billing data requires Stripe to be configured for this deployment. <a href="{{ routeURL "/operator/integrations/stripe/settings" }}">Configure Stripe</a>.</p>
|
|
{{ else }}
|
|
<p class="text-muted mb-0">No subscriptions have been projected yet.</p>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
{{ else }}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>Organization</th>
|
|
<th>Billing Account</th>
|
|
<th>Status</th>
|
|
<th>Current Period</th>
|
|
<th>Cancel at Period End</th>
|
|
<th>Stripe <span class="text-nowrap">Sync<span title="Whether the row has a linked Stripe object. Not Mapped means none is linked yet; that is expected when Stripe is not configured or a sync has not run. Check the Stripe integration settings before assuming something is broken."><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span></span></th>
|
|
<th>Created</th>
|
|
<th>ID</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{ range .Subscriptions }}
|
|
<tr>
|
|
<td>
|
|
{{ if .OrgName }}
|
|
<a href="{{ routeURL "/operator/organizations/{orgID}" .OrgID }}">{{ .OrgName }}</a>
|
|
{{ else }}
|
|
<span class="text-muted">{{ .OrgID }}</span>
|
|
{{ end }}
|
|
</td>
|
|
<td>{{ .BillingAccountName }}</td>
|
|
<td>
|
|
<span class="badge bg-{{ .StatusClass }}">{{ .Status }}</span>
|
|
</td>
|
|
<td>
|
|
{{ if .CurrentPeriodStart }}
|
|
{{ .CurrentPeriodStart }} → {{ .CurrentPeriodEnd }}
|
|
{{ else }}
|
|
<span class="text-muted">—</span>
|
|
{{ end }}
|
|
</td>
|
|
<td>
|
|
{{ if .CancelAtPeriodEnd }}
|
|
<span class="badge text-bg-warning">Yes</span>
|
|
{{ else }}
|
|
<span class="badge bg-secondary">No</span>
|
|
{{ end }}
|
|
</td>
|
|
<td>
|
|
{{ if eq .StripeSyncStatus "synced" }}
|
|
<span class="badge bg-success">Synced</span>
|
|
{{ else if eq .StripeSyncStatus "pending" }}
|
|
<span class="badge text-bg-warning">Pending</span>
|
|
{{ else if eq .StripeSyncStatus "not_mapped" }}
|
|
<span class="badge bg-secondary">Not Mapped</span>
|
|
{{ else }}
|
|
<span class="badge bg-secondary">{{ .StripeSyncStatus }}</span>
|
|
{{ end }}
|
|
</td>
|
|
<td>{{ .CreatedAt }}</td>
|
|
<td><code class="small text-body-secondary" title="{{ .SubscriptionID }}">{{ slice .SubscriptionID 0 8 }}...</code></td>
|
|
</tr>
|
|
{{ end }}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{ template "listPager" .Nav }}
|
|
{{ end }}
|
|
{{ end }}
|