/** * 治理台取数 —— 每个查询一把键,写动作后按键失效。 * 前端零过程状态:所有画面都是服务端投影的函数,刷新即重建(§9.2)。 */ import { useQuery } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query'; import type { ClosureReport, GovActivitiesOutput, GovApprovalsOutput, GovChannelsOutput, GovGrantsOutput, GovLedgerPageOutput, GovLlmOutput, GovMemoryOutput, GovMetricsOutput, GovObjectsOutput, GovPackagesOutput, GovStaffOutput, GovTaskLogOutput, GovTasksOutput, GovToolsOutput, GovTriggersOutput, GovWorkGroupsOutput, GovWorkflowOutput, RouteOutput, ProcessPackage, GovLedgerByActivityOutput, } from '@contracts'; import type { ApprovalTemplate } from '@contracts'; import { browseLedgerRoute, ledgerByActivityRoute, govMetricsRoute, govTaskLogRoute, govWorkflowRoute, listActivitiesRoute, listApprovalTemplatesRoute, listActivityMemoryRoute, listApprovalsRoute, listChannelsRoute, listGovTasksRoute, listGrantsRoute, listLedgerObjectsRoute, listLlmInstancesRoute, listLlmProvidersRoute, listProcessPackagesRoute, getOrgUserRoute, listOrgGroupMembersRoute, listOrgGroupsRoute, listOrgRolesRoute, listOrgUsersRoute, listRoleAccessRoute, listStaffRoute, listToolsRoute, listTriggersRoute, listWorkGroupsRoute, validateProcessRoute, } from '@contracts'; import { call } from '@web-shared/typed-fetch.ts'; export type GovTasks = GovTasksOutput; export type GovApprovals = GovApprovalsOutput; export type GovStaff = GovStaffOutput; export type GovActivities = GovActivitiesOutput; export type GovTools = GovToolsOutput; export type GovObjects = GovObjectsOutput; export type GovLedgerPage = GovLedgerPageOutput; export type GovLedgerByActivity = GovLedgerByActivityOutput; export type GovMemory = GovMemoryOutput; export type GovMetrics = GovMetricsOutput; export type GovChannels = GovChannelsOutput; export type GovLlm = GovLlmOutput; export type GovWorkflow = GovWorkflowOutput; export type GovTaskLog = GovTaskLogOutput; export type GovGrants = GovGrantsOutput; /** `enabled: false` 供设计中心用 —— 那一面不看运行态,别去要一份自己无权也无用的数据 */ export const useGovTasks = (opts?: { enabled?: boolean }): UseQueryResult => useQuery({ queryKey: ['gov', 'tasks'], queryFn: () => call(listGovTasksRoute), enabled: opts?.enabled ?? true, }); export const useGovApprovals = (): UseQueryResult => useQuery({ queryKey: ['gov', 'approvals'], queryFn: () => call(listApprovalsRoute) }); export const useGovStaff = (): UseQueryResult => useQuery({ queryKey: ['gov', 'staff'], queryFn: () => call(listStaffRoute) }); export const useGovGrants = (positionId: string | undefined): UseQueryResult => useQuery({ queryKey: ['gov', 'grants', positionId], queryFn: () => call(listGrantsRoute, { params: { positionId: positionId! } }), enabled: positionId !== undefined, }); export const useGovPackages = (): UseQueryResult => useQuery({ queryKey: ['gov', 'process'], queryFn: () => call(listProcessPackagesRoute) }); /** * 每个包一份闭合报告 —— 磁贴上的「边 n/m」、待补清单、发布门、驾驶舱的未闭合草稿数都读它。 * 一处算、多处读:两张首页各算一遍就会各说各话。 */ export const useGovPkgReports = ( packages: readonly ProcessPackage[], ): UseQueryResult> => useQuery({ queryKey: ['gov', 'process-reports', packages.map((p) => `${p.id}@${p.version}`).join(',')], enabled: packages.length > 0, queryFn: async () => { const entries = await Promise.all( packages.map( async (p) => [`${p.id}@${p.version}`, await call(validateProcessRoute, { body: { pkg: p } })] as const, ), ); return new Map(entries); }, }); export type GovWorkGroups = GovWorkGroupsOutput; export const useGovWorkGroups = (): UseQueryResult => useQuery({ queryKey: ['gov', 'work-groups'], queryFn: () => call(listWorkGroupsRoute) }); export const useGovTriggers = (): UseQueryResult => useQuery({ queryKey: ['gov', 'triggers'], queryFn: () => call(listTriggersRoute) }); export const useGovActivities = (): UseQueryResult => useQuery({ queryKey: ['gov', 'activities'], queryFn: () => call(listActivitiesRoute) }); /** 审批模板(审批活动)—— 活动库的第二格、编排里审批点的选项 */ export type GovApprovalTemplates = { templates: ReadonlyArray }> }; export const useGovApprovalTemplates = (): UseQueryResult => useQuery({ queryKey: ['gov', 'approval-templates'], queryFn: () => call(listApprovalTemplatesRoute), }); export const useGovTools = (): UseQueryResult => useQuery({ queryKey: ['gov', 'tools'], queryFn: () => call(listToolsRoute) }); export const useGovObjects = (opts?: { enabled?: boolean }): UseQueryResult => useQuery({ queryKey: ['gov', 'objects'], queryFn: () => call(listLedgerObjectsRoute), enabled: opts?.enabled ?? true, }); /** * **按活动看数据域**(2026-08-24)—— 「我这个活动写了哪几张表,现在各有几行」。 * 键挂在 `['gov','data','by-activity']` 下:`kind:'system'` 的推送按 `gov` 前缀失效, * 一单跑完这一屏跟着变(数据域不以任务为轴,靠的就是那条系统级广播)。 */ export const useGovDataByActivity = (): UseQueryResult => useQuery({ queryKey: ['gov', 'data', 'by-activity'], queryFn: () => call(ledgerByActivityRoute), }); export const useGovLedger = (domain: string | undefined, object: string | undefined): UseQueryResult => useQuery({ queryKey: ['gov', 'ledger', domain, object], queryFn: () => call(browseLedgerRoute, { params: { domain: domain!, object: object! } }), enabled: domain !== undefined && object !== undefined, }); export const useGovMemory = (activityId: string | undefined): UseQueryResult => useQuery({ queryKey: ['gov', 'memory', activityId], queryFn: () => call(listActivityMemoryRoute, { params: { activityId: activityId! } }), enabled: activityId !== undefined, }); export const useGovMetrics = (): UseQueryResult => useQuery({ queryKey: ['gov', 'metrics'], queryFn: () => call(govMetricsRoute) }); export const useGovChannels = (): UseQueryResult => useQuery({ queryKey: ['gov', 'channels'], queryFn: () => call(listChannelsRoute) }); /** Provider 档案 —— 建实例表单预填默认端点用;一次取,基本不变 */ export type GovLlmProviders = { providers: ReadonlyArray<{ provider: string; label: string; defaultBaseUrl: string; structuredOutput: string }> }; export const useGovLlmProviders = (): UseQueryResult => useQuery({ queryKey: ['gov', 'llm-providers'], queryFn: () => call(listLlmProvidersRoute), staleTime: 10 * 60_000, }); export const useGovLlm = (): UseQueryResult => useQuery({ queryKey: ['gov', 'llm'], queryFn: () => call(listLlmInstancesRoute) }); export const useGovWorkflow = (id: string): UseQueryResult => useQuery({ queryKey: ['gov', 'workflow', id], queryFn: () => call(govWorkflowRoute, { params: { id } }) }); export const useGovTaskLog = (taskId: string): UseQueryResult => useQuery({ queryKey: ['gov', 'tasklog', taskId], queryFn: () => call(govTaskLogRoute, { params: { taskId } }) }); /* ── 成员与组织(ADR-012)———————————————————————————————————————— */ /** * 各屏的查询键 —— 写完**按碰到的那几屏**失效,别一把 `['gov','org']` 全清。 * 成员那一趟最贵(服务端要逐人问角色,见 debt),把它拖进来重取, * 屏上那棵组织树就多钉着已经没了的组好几秒。 */ export const ORG_KEYS = { users: ['gov', 'org', 'users'] as const, user: ['gov', 'org', 'user'] as const, roles: ['gov', 'org', 'roles'] as const, groups: ['gov', 'org', 'groups'] as const, groupMembers: ['gov', 'org', 'group-members'] as const, roleAccess: ['gov', 'org', 'role-access'] as const, }; export type GovOrgUsers = RouteOutput; export type GovOrgUserDetail = RouteOutput; export type GovOrgRoles = RouteOutput; export type GovOrgGroups = RouteOutput; export type GovOrgGroupMembers = RouteOutput; export type GovRoleAccess = RouteOutput; /** * 成员列表 —— 检索与「含停用」都在**服务端**过滤(那边握着全量,前端只拿要摆的那些)。 * 服务端第一次取要几秒(Keycloak 没有批量取角色的口,见 debt),它自己有 60 秒缓存; * 这边 `staleTime` 跟着设一档,免得同一屏的几个组件各要一遍。 */ export const useOrgUsers = (q: string, includeDisabled: boolean): UseQueryResult => useQuery({ queryKey: ['gov', 'org', 'users', q, includeDisabled], queryFn: () => call(listOrgUsersRoute, { query: { q: q === '' ? undefined : q, includeDisabled: includeDisabled ? '1' : undefined }, }), staleTime: 30_000, }); export const useOrgUser = (sub: string | undefined): UseQueryResult => useQuery({ queryKey: ['gov', 'org', 'user', sub], queryFn: () => call(getOrgUserRoute, { params: { sub: sub! } }), enabled: sub !== undefined, }); export const useOrgRoles = (): UseQueryResult => useQuery({ queryKey: ['gov', 'org', 'roles'], queryFn: () => call(listOrgRolesRoute), staleTime: 30_000 }); export const useOrgGroups = (): UseQueryResult => useQuery({ queryKey: ['gov', 'org', 'groups'], queryFn: () => call(listOrgGroupsRoute), staleTime: 30_000 }); export const useOrgGroupMembers = (groupId: string | undefined): UseQueryResult => useQuery({ queryKey: ['gov', 'org', 'group-members', groupId], queryFn: () => call(listOrgGroupMembersRoute, { params: { id: groupId! } }), enabled: groupId !== undefined, }); /** 角色 → 端 —— 这张表是**本地**的(ADR-012),不是 Keycloak 的字段 */ export const useRoleAccess = (): UseQueryResult => useQuery({ queryKey: ['gov', 'org', 'role-access'], queryFn: () => call(listRoleAccessRoute) });