fix(web): error/404 экраны, сплит бандла и bulk-мутации
- RouteErrorComponent/RouteNotFoundComponent на root-маршруте (RU-копирайт, retry + переход на главную; redirect в портал не мигает ошибкой) - settings: форма инициализируется один раз — фоновый refetch больше не затирает ввод пользователя - code splitting: autoCodeSplitting роутов + manualChunks (react/router/query/ charts/dnd); вход ~507KB вместо единого чанка 1.57MB, recharts (330KB) грузится лениво; версия recharts в packages/ui выровнена с apps/web (3.8.0) - bulk-эндпоинты: POST /agents/approve-bulk и PUT /policy-sets/:id/agents — назначение набора агентам одним запросом вместо N×(GET+PUT) - оптимистичные обновления с rollback: approve, approve-bulk, удаление агента, переключение набора - тесты bulk-операций (45 passed)
This commit is contained in:
@@ -125,21 +125,46 @@ function AgentsPage() {
|
||||
[navigate],
|
||||
)
|
||||
|
||||
/** Optimistically patch the agents list; returns a rollback fn. */
|
||||
const patchAgentsCache = useCallback(
|
||||
(patch: (items: Agent[]) => Agent[]) => {
|
||||
const prev = qc.getQueryData<{ items: Agent[] }>(['agents'])
|
||||
if (prev) qc.setQueryData(['agents'], { items: patch(prev.items) })
|
||||
return () => {
|
||||
if (prev) qc.setQueryData(['agents'], prev)
|
||||
}
|
||||
},
|
||||
[qc],
|
||||
)
|
||||
|
||||
const approve = useMutation({
|
||||
mutationFn: (id: string) =>
|
||||
apiFetch(`/api/v1/agents/${id}/approve`, { method: 'POST' }),
|
||||
onMutate: (id) =>
|
||||
patchAgentsCache((items) =>
|
||||
items.map((a) => (a.id === id ? { ...a, status: 'approved' } : a)),
|
||||
),
|
||||
onSuccess: () => {
|
||||
toast.success('Агент одобрен')
|
||||
void qc.invalidateQueries({ queryKey: ['agents'] })
|
||||
},
|
||||
onError: (e: Error) => toast.error(e.message),
|
||||
onError: (e: Error, _id, rollback) => {
|
||||
rollback?.()
|
||||
toast.error(e.message)
|
||||
},
|
||||
})
|
||||
|
||||
const approveAllPending = useMutation({
|
||||
mutationFn: async (ids: string[]) => {
|
||||
await Promise.all(
|
||||
ids.map((id) =>
|
||||
apiFetch(`/api/v1/agents/${id}/approve`, { method: 'POST' }),
|
||||
mutationFn: (ids: string[]) =>
|
||||
apiFetch('/api/v1/agents/approve-bulk', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ agent_ids: ids }),
|
||||
}),
|
||||
onMutate: (ids) => {
|
||||
const target = new Set(ids)
|
||||
return patchAgentsCache((items) =>
|
||||
items.map((a) =>
|
||||
target.has(a.id) ? { ...a, status: 'approved' } : a,
|
||||
),
|
||||
)
|
||||
},
|
||||
@@ -147,12 +172,16 @@ function AgentsPage() {
|
||||
toast.success('Все pending одобрены')
|
||||
void qc.invalidateQueries({ queryKey: ['agents'] })
|
||||
},
|
||||
onError: (e: Error) => toast.error(e.message),
|
||||
onError: (e: Error, _ids, rollback) => {
|
||||
rollback?.()
|
||||
toast.error(e.message)
|
||||
},
|
||||
})
|
||||
|
||||
const removeAgent = useMutation({
|
||||
mutationFn: (id: string) =>
|
||||
apiFetch(`/api/v1/agents/${id}`, { method: 'DELETE' }),
|
||||
onMutate: (id) => patchAgentsCache((items) => items.filter((a) => a.id !== id)),
|
||||
onSuccess: (_data, id) => {
|
||||
toast.success('Агент удалён')
|
||||
setDeleteAgentId(null)
|
||||
@@ -162,7 +191,10 @@ function AgentsPage() {
|
||||
void qc.invalidateQueries({ queryKey: ['agents'] })
|
||||
void qc.invalidateQueries({ queryKey: ['dashboard'] })
|
||||
},
|
||||
onError: (e: Error) => toast.error(e.message),
|
||||
onError: (e: Error, _id, rollback) => {
|
||||
rollback?.()
|
||||
toast.error(e.message)
|
||||
},
|
||||
})
|
||||
|
||||
const items = agentsQ.data?.items ?? []
|
||||
|
||||
Reference in New Issue
Block a user