Files
govoplan-views/webui/src/api/views.ts
T

386 lines
9.8 KiB
TypeScript

import {
apiFetch,
apiPath,
type ApiSettings,
type EffectiveViewProjection,
type NavigationPreferences,
type ViewPresentation
} from "@govoplan/core-webui";
export type ViewScopeType = "system" | "tenant" | "group" | "user";
export type ViewAssignmentScopeType = "system" | "tenant" | "group" | "user";
export type ViewAssignmentMode = "available" | "default" | "required";
export type ViewRevision = {
id: string;
definition_id: string;
revision: number;
surface_contract_version: string;
visible_surface_ids: string[];
presentation: {
navigation?: NavigationPreferences | null;
navigation_mode?: "grouped" | "flat";
product_area_order?: string[];
product_area_labels?: Record<string, string>;
quick_access_recommended_tool_ids?: string[];
quick_access_focused_tool_ids?: string[];
};
content_hash: string;
created_by?: string | null;
created_at: string;
};
export type ViewDefinition = {
id: string;
tenant_id?: string | null;
scope_type: ViewScopeType;
scope_id?: string | null;
definition_key: string;
name: string;
description?: string | null;
status: "draft" | "published" | "archived";
current_revision: number;
latest_revision: ViewRevision;
published_revision?: ViewRevision | null;
readonly: boolean;
stale_surface_ids: string[];
created_by?: string | null;
updated_by?: string | null;
created_at: string;
updated_at: string;
};
export type ViewAssignment = {
id: string;
tenant_id?: string | null;
scope_type: ViewAssignmentScopeType;
scope_id?: string | null;
scope_label?: string | null;
scope_detail?: string | null;
definition_id: string;
revision_id?: string | null;
mode: ViewAssignmentMode;
priority: number;
is_active: boolean;
metadata: Record<string, unknown>;
created_by?: string | null;
updated_by?: string | null;
created_at: string;
updated_at: string;
};
export type ViewAssignmentTarget = {
id: string;
scope_type: "group" | "user";
label: string;
detail?: string | null;
disabled: boolean;
disabled_reason?: string | null;
};
export type ViewAssignmentTargetList = {
directory_available: boolean;
targets: ViewAssignmentTarget[];
unavailable_reason?: string | null;
};
type EffectiveViewApiResponse = {
active_view_id?: string | null;
active_revision_id?: string | null;
active_view_name?: string | null;
visible_surface_ids: string[];
presentation?: ViewRevision["presentation"];
locked: boolean;
available_views: Array<{
id: string;
name: string;
description?: string | null;
revision_id: string;
}>;
provenance: Array<{
source: string;
scope_type?: string | null;
scope_id?: string | null;
detail?: string | null;
}>;
diagnostics?: Array<{
severity: "warning" | "error";
code: string;
message: string;
surface_ids: string[];
}>;
};
function jsonBody(payload: unknown): RequestInit {
return {
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
};
}
function projection(response: EffectiveViewApiResponse): EffectiveViewProjection {
return {
activeViewId: response.active_view_id ?? null,
activeRevisionId: response.active_revision_id ?? null,
activeViewName: response.active_view_name ?? null,
visibleSurfaceIds: response.visible_surface_ids,
presentation: presentationFromApi(response.presentation),
locked: response.locked,
availableViews: response.available_views.map((view) => ({
id: view.id,
name: view.name,
description: view.description,
revisionId: view.revision_id
})),
provenance: response.provenance.map((item) => ({
source: item.source,
scopeType: item.scope_type,
scopeId: item.scope_id,
detail: item.detail
})),
diagnostics: (response.diagnostics ?? []).map((item) => ({
severity: item.severity,
code: item.code,
message: item.message,
surfaceIds: item.surface_ids
}))
};
}
export async function fetchEffectiveView(
settings: ApiSettings
): Promise<EffectiveViewProjection> {
return projection(
await apiFetch<EffectiveViewApiResponse>(
settings,
"/api/v1/views/effective",
{ cache: "no-store" }
)
);
}
export async function selectEffectiveView(
settings: ApiSettings,
viewId: string | null
): Promise<EffectiveViewProjection> {
return projection(
await apiFetch<EffectiveViewApiResponse>(
settings,
"/api/v1/views/selection",
{
method: "PUT",
...jsonBody({ view_id: viewId })
}
)
);
}
export async function resolveWorkflowView(
settings: ApiSettings,
context: {
viewId: string;
revisionId?: string | null;
visibleSurfaceIds?: string[];
}
): Promise<EffectiveViewProjection> {
return projection(
await apiFetch<EffectiveViewApiResponse>(
settings,
"/api/v1/views/effective/workflow",
{
method: "POST",
...jsonBody({
view_id: context.viewId,
revision_id: context.revisionId ?? null,
visible_surface_ids: context.visibleSurfaceIds ?? []
})
}
)
);
}
export async function fetchViewDefinitions(
settings: ApiSettings,
scopeType: ViewScopeType,
scopeId?: string | null
): Promise<ViewDefinition[]> {
const response = await apiFetch<{ definitions: ViewDefinition[] }>(
settings,
apiPath("/api/v1/views/definitions", {
scope_type: scopeType,
scope_id: scopeId || undefined,
include_inherited: scopeType === "tenant"
})
);
return response.definitions;
}
export function createViewDefinition(
settings: ApiSettings,
payload: {
scope_type: ViewScopeType;
scope_id?: string | null;
name: string;
description?: string | null;
visible_surface_ids: string[];
presentation?: ViewRevision["presentation"];
}
): Promise<ViewDefinition> {
return apiFetch(settings, "/api/v1/views/definitions", {
method: "POST",
...jsonBody(payload)
});
}
export function updateViewDefinition(
settings: ApiSettings,
definitionId: string,
payload: { name: string; description?: string | null }
): Promise<ViewDefinition> {
return apiFetch(settings, `/api/v1/views/definitions/${definitionId}`, {
method: "PATCH",
...jsonBody(payload)
});
}
export function createViewRevision(
settings: ApiSettings,
definitionId: string,
visibleSurfaceIds: string[],
presentation: ViewPresentation
): Promise<ViewDefinition> {
return apiFetch(
settings,
`/api/v1/views/definitions/${definitionId}/revisions`,
{
method: "POST",
...jsonBody({
visible_surface_ids: visibleSurfaceIds,
presentation: presentationToApi(presentation)
})
}
);
}
export function presentationFromApi(
value: ViewRevision["presentation"] | undefined
): ViewPresentation {
return {
navigation: value?.navigation ?? null,
navigationMode: value?.navigation_mode,
productAreaOrder: value?.product_area_order ?? [],
productAreaLabels: value?.product_area_labels ?? {},
quickAccessRecommendedToolIds: value?.quick_access_recommended_tool_ids ?? [],
quickAccessFocusedToolIds: value?.quick_access_focused_tool_ids ?? []
};
}
export function presentationToApi(
value: ViewPresentation
): ViewRevision["presentation"] {
return {
navigation: value.navigation ?? null,
navigation_mode: value.navigationMode ?? "grouped",
product_area_order: value.productAreaOrder ?? [],
product_area_labels: value.productAreaLabels ?? {},
quick_access_recommended_tool_ids: value.quickAccessRecommendedToolIds ?? [],
quick_access_focused_tool_ids: value.quickAccessFocusedToolIds ?? []
};
}
export function publishViewRevision(
settings: ApiSettings,
definitionId: string,
revisionId: string
): Promise<ViewDefinition> {
return apiFetch(
settings,
`/api/v1/views/definitions/${definitionId}/revisions/${revisionId}/publish`,
{ method: "POST" }
);
}
export function archiveViewDefinition(
settings: ApiSettings,
definitionId: string
): Promise<ViewDefinition> {
return apiFetch(settings, `/api/v1/views/definitions/${definitionId}`, {
method: "DELETE"
});
}
export async function fetchViewAssignments(
settings: ApiSettings,
scopeType: ViewScopeType
): Promise<ViewAssignment[]> {
const response = await apiFetch<{ assignments: ViewAssignment[] }>(
settings,
apiPath("/api/v1/views/assignments", {
scope_type: scopeType,
include_inherited: scopeType === "tenant"
})
);
return response.assignments;
}
export function fetchViewAssignmentTargets(
settings: ApiSettings,
scopeType: "group" | "user",
query: string,
limit: number,
signal?: AbortSignal
): Promise<ViewAssignmentTargetList> {
return apiFetch(
settings,
apiPath("/api/v1/views/assignment-targets", {
scope_type: scopeType,
query: query || undefined,
limit
}),
{ signal }
);
}
export function createViewAssignment(
settings: ApiSettings,
payload: {
scope_type: ViewAssignmentScopeType;
scope_id?: string | null;
definition_id: string;
revision_id?: string | null;
mode: ViewAssignmentMode;
priority: number;
is_active: boolean;
}
): Promise<ViewAssignment> {
return apiFetch(settings, "/api/v1/views/assignments", {
method: "POST",
...jsonBody(payload)
});
}
export function updateViewAssignment(
settings: ApiSettings,
assignmentId: string,
payload: {
revision_id?: string | null;
mode?: ViewAssignmentMode;
priority?: number;
is_active?: boolean;
}
): Promise<ViewAssignment> {
return apiFetch(settings, `/api/v1/views/assignments/${assignmentId}`, {
method: "PATCH",
...jsonBody(payload)
});
}
export function deleteViewAssignment(
settings: ApiSettings,
assignmentId: string
): Promise<void> {
return apiFetch(settings, `/api/v1/views/assignments/${assignmentId}`, {
method: "DELETE"
});
}