fix(ux): friendly error UI вместо raw AxiosError

This commit is contained in:
Александр Зимин
2026-05-12 12:41:48 +00:00
parent db0986d235
commit 3984d99004
14 changed files with 274 additions and 57 deletions
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
import { Alert, Badge, LoadingBlock, Modal } from '@/ui'
import { Badge, LoadingBlock, Modal, QueryErrorState } from '@/ui'
import { useChangelogDiff } from '@/api/queries'
import { kindBadgeVariant, kindLabel } from './kind-meta'
@@ -28,7 +28,7 @@ export function ChangelogDiffModal({
entryId: number | undefined
}) {
const { t } = useTranslation()
const { data, isLoading, error } = useChangelogDiff(
const { data, isLoading, error, refetch } = useChangelogDiff(
open ? dictionaryName : undefined,
open ? entryId : undefined,
)
@@ -46,11 +46,7 @@ export function ChangelogDiffModal({
}
>
{isLoading && <LoadingBlock size="md" label={t('loading')} />}
{error && (
<Alert variant="error" title={t('error.failed')}>
{String(error)}
</Alert>
)}
{error && <QueryErrorState error={error} onRetry={() => refetch()} />}
{data && (
<div className="space-y-4">
{/* Header strip — kind + author */}
@@ -13,7 +13,7 @@ import {
} from 'd3-force'
import { useDictionaries, dictionaryDependentsQuery, recordsQuery } from '@/api/queries'
import type { DictionaryDefinition, SchemaDependent } from '@/api/client'
import { GlobeLoader, LoadingBlock } from '@/ui'
import { GlobeLoader, LoadingBlock, QueryErrorState } from '@/ui'
import { cn } from '@/lib/utils'
// 3D-режим лениво грузится — three.js (~650kb) и react-force-graph-3d не
@@ -165,7 +165,7 @@ export function DictionaryGraph() {
}
if (dictsQuery.error) {
return <div className="text-body text-pink">Ошибка загрузки: {String(dictsQuery.error)}</div>
return <QueryErrorState error={dictsQuery.error} onRetry={() => dictsQuery.refetch()} />
}
if (dicts.length === 0) {
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from '@tanstack/react-router'
import { Alert, Badge, Button, LoadingBlock } from '@/ui'
import { Badge, Button, LoadingBlock, QueryErrorState } from '@/ui'
import { CaretLeftIcon, CaretRightIcon, ArrowSquareOutIcon } from '@phosphor-icons/react'
import { useRecordDependents } from '@/api/queries'
import type { OnCloseAction } from '@/api/client'
@@ -32,7 +32,7 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
const { t } = useTranslation()
const [page, setPage] = useState(0)
const size = 20
const { data, isLoading, error } = useRecordDependents(
const { data, isLoading, error, refetch } = useRecordDependents(
dictionaryName,
businessKey,
page,
@@ -42,11 +42,7 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
if (!businessKey) return null
if (isLoading) return <LoadingBlock size="sm" label={t('loading')} />
if (error) {
return (
<Alert variant="error" title={t('error.failed')}>
{String(error)}
</Alert>
)
return <QueryErrorState error={error} onRetry={() => refetch()} />
}
if (!data || data.total === 0) return null
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, Badge, Button, Drawer, LoadingBlock } from '@/ui'
import { Badge, Button, Drawer, LoadingBlock, QueryErrorState } from '@/ui'
import { ArrowsLeftRightIcon, ArrowCounterClockwiseIcon, BracketsCurlyIcon, InfoIcon } from '@phosphor-icons/react'
import { useRecordHistory } from '@/api/queries'
import { RecordDependentsPanel } from '@/components/lineage/RecordDependentsPanel'
@@ -23,7 +23,7 @@ export const RecordHistoryDrawer = ({
open, onClose, dictionaryName, businessKey, onCompare, onRevert,
}: Props) => {
const { t } = useTranslation()
const { data, isLoading, error } = useRecordHistory(dictionaryName, open ? businessKey : undefined)
const { data, isLoading, error, refetch } = useRecordHistory(dictionaryName, open ? businessKey : undefined)
// Per-row collapsible JSON viewer — выносим из <details> чтобы хранить
// expanded state поверх renders.
const [expanded, setExpanded] = useState<Set<string>>(new Set())
@@ -48,11 +48,7 @@ export const RecordHistoryDrawer = ({
)}
{isLoading && <LoadingBlock size="md" label={t('loading')} />}
{error && (
<Alert variant="error" title={t('error.failed')}>
{String(error)}
</Alert>
)}
{error && <QueryErrorState error={error} onRetry={() => refetch()} />}
{data && data.length === 0 && (
<p className="text-body text-mute">{t('history.empty')}</p>
)}
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { Alert, Badge, Button, Drawer, LoadingBlock, TextArea, toast } from '@/ui'
import { Badge, Button, Drawer, LoadingBlock, QueryErrorState, TextArea, toast } from '@/ui'
import {
CheckIcon,
PaperPlaneTiltIcon,
@@ -69,7 +69,7 @@ export function SchemaDraftDrawer({
const { t } = useTranslation()
const [comment, setComment] = useState('')
const [editOpen, setEditOpen] = useState(false)
const { data: draft, isLoading, error } = useDictActiveSchemaDraft(
const { data: draft, isLoading, error, refetch } = useDictActiveSchemaDraft(
open ? dictionaryName : undefined,
)
@@ -204,11 +204,7 @@ export function SchemaDraftDrawer({
>
<div className="space-y-4">
{isLoading && <LoadingBlock size="md" label={t('loading')} />}
{error && (
<Alert variant="error" title={t('error.failed')}>
{String(error)}
</Alert>
)}
{error && <QueryErrorState error={error} onRetry={() => refetch()} />}
{!isLoading && !draft && (
<p className="text-body text-mute">
{t('workflow.schemaDraft.empty', {