import { filterSearchableSelectOptions, unavailableReferenceOption, type ApiSettings, type ConfigurationReferenceSelectorsUiCapability, type ReferenceOption, type ReferenceOptionProvider } from "@govoplan/core-webui"; import { fetchConfigurationChanges, fetchTenants, type ConfigurationChangeRequest } from "../../api/admin"; export const configurationReferenceSelectors: ConfigurationReferenceSelectorsUiCapability = { tenantProvider: createTenantReferenceProvider, changeRequestProvider: createChangeRequestReferenceProvider }; export function createTenantReferenceProvider( settings: ApiSettings ): ReferenceOptionProvider { async function catalogue(signal: AbortSignal): Promise { const tenants = await fetchTenants(settings); if (signal.aborted) throw abortError(); return tenants.map((tenant) => ({ value: tenant.id, label: tenant.name || tenant.slug || tenant.id, description: [ tenant.slug, tenant.is_active ? null : "Inactive", tenant.id ].filter(Boolean).join(" · "), kind: "tenant", availability: tenant.is_active ? "available" : "inactive", disabled: !tenant.is_active, sourceModule: "tenancy", provenance: { tenantId: tenant.id, active: tenant.is_active } })); } return { async search(query, context) { const options = await catalogue(context.signal); return retainSelected( filterSearchableSelectOptions(options, query, context.limit), options, context.selectedValues ); }, async resolve(values, context) { const options = await catalogue(context.signal); const byValue = new Map(options.map((option) => [option.value, option])); return values.map( (value) => byValue.get(value) ?? unavailableReferenceOption(value, "Unavailable tenant") ); } }; } export function createChangeRequestReferenceProvider( settings: ApiSettings, { purpose, tenantId }: { purpose: string; tenantId?: string | null; } ): ReferenceOptionProvider { async function catalogue(signal: AbortSignal): Promise<{ eligible: ReferenceOption[]; all: ReferenceOption[]; }> { const response = await fetchConfigurationChanges(settings); if (signal.aborted) throw abortError(); const matching = response.requests.filter( (request) => request.key === purpose && requestTargetsTenant(request, tenantId) ); const all = matching.map(changeRequestOption); return { eligible: all.filter((option) => !option.disabled), all }; } return { async search(query, context) { const options = await catalogue(context.signal); return retainSelected( filterSearchableSelectOptions( options.eligible, query, context.limit ), options.all, context.selectedValues ); }, async resolve(values, context) { const options = await catalogue(context.signal); const byValue = new Map( options.all.map((option) => [option.value, option]) ); return values.map( (value) => byValue.get(value) ?? unavailableReferenceOption( value, "Unavailable configuration request" ) ); } }; } function changeRequestOption( request: ConfigurationChangeRequest ): ReferenceOption { const closed = request.status === "applied" || request.status === "rejected"; const eligible = request.status === "approved" && request.dry_run; return { value: request.id, label: request.label || request.key, description: [ request.status.split("_").join(" "), request.dry_run ? null : "dry run not recorded", formatTimestamp(request.requested_at), `requested by ${request.requested_by}`, request.id ].filter(Boolean).join(" · "), searchText: `${request.id} ${request.requested_by} ${request.status}`, kind: "configuration_change_request", availability: closed ? "unavailable" : eligible ? "available" : "inactive", disabled: !eligible, sourceModule: "admin", provenance: { purpose: request.key, target: request.target ?? {}, requestedBy: request.requested_by, requestedAt: request.requested_at, status: request.status, dryRunRecorded: request.dry_run } }; } function requestTargetsTenant( request: ConfigurationChangeRequest, tenantId?: string | null ): boolean { if (!tenantId) return true; const targetTenant = request.target?.tenant_id; return !targetTenant || String(targetTenant) === tenantId; } function retainSelected( matches: readonly ReferenceOption[], catalogue: readonly ReferenceOption[], selectedValues: readonly string[] ): ReferenceOption[] { const result = [...matches]; const returned = new Set(result.map((option) => option.value)); const byValue = new Map(catalogue.map((option) => [option.value, option])); for (const value of selectedValues) { if (returned.has(value)) continue; result.push( byValue.get(value) ?? unavailableReferenceOption(value) ); } return result; } function formatTimestamp(value: string): string { const date = new Date(value); return Number.isNaN(date.getTime()) ? value : date.toLocaleString(); } function abortError(): DOMException { return new DOMException("The operation was aborted.", "AbortError"); }