feat: refactor components to utilize DataGridCard for improved UI consistency
CI / changes (push) Successful in 11s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 57s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m27s
CI / changes (push) Successful in 11s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 57s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m27s
Updated various components including AccessApiKeysCard, FirewallClientsGrid, FirewallRulesGrid, ModuleEntriesSection, and DirectoriesComponent to replace traditional card and table structures with the new DataGridCard component. This change enhances the user interface by providing a more consistent layout and improved loading states. Additionally, integrated QueryState for better handling of loading and error scenarios across these components, streamlining the overall user experience.
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
import { ColumnDef, useReactTable } from '@tanstack/react-table'
|
||||
import { Pencil, Trash2 } from 'lucide-react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { Button } from '@evobgp/ui/components/button'
|
||||
|
||||
import { DataGridShell } from '@/components/data-grid-shell'
|
||||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||||
import { formatDateTime } from '@/lib/modules/display'
|
||||
import { communityLabel } from '@/lib/modules/helpers'
|
||||
import { createClientDataGridOptions } from '@/lib/data-grid-defaults'
|
||||
import type {
|
||||
AsEntry,
|
||||
BgpCommunity,
|
||||
CdnSource,
|
||||
DomainEntry,
|
||||
IpRangeEntry,
|
||||
ModuleRow,
|
||||
} from '@/types/api'
|
||||
|
||||
type DeleteTarget =
|
||||
| { kind: 'domain'; entry: DomainEntry }
|
||||
| { kind: 'ip-range'; entry: IpRangeEntry }
|
||||
| { kind: 'cdn'; entry: CdnSource }
|
||||
| { kind: 'as'; entry: AsEntry }
|
||||
|
||||
function RowActions({ onEdit, onDelete }: { onEdit: () => void; onDelete: () => void }) {
|
||||
return (
|
||||
<div className="flex justify-end gap-1">
|
||||
<Button variant="ghost" size="icon-sm" type="button" onClick={onEdit} aria-label="Редактировать">
|
||||
<Pencil className="size-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
className="text-destructive"
|
||||
onClick={onDelete}
|
||||
aria-label="Удалить"
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ModuleEntriesGrid({
|
||||
mod,
|
||||
rows,
|
||||
communities,
|
||||
onEdit,
|
||||
onDelete,
|
||||
isLoading = false,
|
||||
}: {
|
||||
mod: ModuleRow
|
||||
rows: Record<string, unknown>[]
|
||||
communities: BgpCommunity[]
|
||||
onEdit: (target: DeleteTarget) => void
|
||||
onDelete: (target: DeleteTarget) => void
|
||||
isLoading?: boolean
|
||||
}) {
|
||||
const columns = useMemo(() => {
|
||||
if (mod.type === 'DOMAINS') {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'fqdn',
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataGridColumnHeader column={column as never} title="FQDN" />
|
||||
),
|
||||
cell: ({ row }: { row: { original: DomainEntry } }) => (
|
||||
<span className="font-mono text-sm">{row.original.fqdn}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'community',
|
||||
header: 'Community',
|
||||
cell: ({ row }: { row: { original: DomainEntry } }) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{communityLabel(row.original.community_id, communities)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
enableSorting: false,
|
||||
header: () => null,
|
||||
cell: ({ row }: { row: { original: DomainEntry } }) => (
|
||||
<RowActions
|
||||
onEdit={() => onEdit({ kind: 'domain', entry: row.original })}
|
||||
onDelete={() => onDelete({ kind: 'domain', entry: row.original })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<DomainEntry>[]
|
||||
}
|
||||
|
||||
if (mod.type === 'IP_RANGES') {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'prefix',
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataGridColumnHeader column={column as never} title="Префикс (CIDR)" />
|
||||
),
|
||||
cell: ({ row }: { row: { original: IpRangeEntry } }) => (
|
||||
<span className="font-mono text-sm">{row.original.prefix}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'community',
|
||||
header: 'Community',
|
||||
cell: ({ row }: { row: { original: IpRangeEntry } }) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{communityLabel(row.original.community_id, communities)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
enableSorting: false,
|
||||
header: () => null,
|
||||
cell: ({ row }: { row: { original: IpRangeEntry } }) => (
|
||||
<RowActions
|
||||
onEdit={() => onEdit({ kind: 'ip-range', entry: row.original })}
|
||||
onDelete={() => onDelete({ kind: 'ip-range', entry: row.original })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<IpRangeEntry>[]
|
||||
}
|
||||
|
||||
if (mod.type === 'CDN_CIDRS') {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'url',
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataGridColumnHeader column={column as never} title="URL" />
|
||||
),
|
||||
cell: ({ row }: { row: { original: CdnSource } }) => (
|
||||
<span className="max-w-xs truncate font-mono text-xs">{row.original.url}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'source_kind',
|
||||
header: 'Тип',
|
||||
cell: ({ row }: { row: { original: CdnSource } }) => (
|
||||
<span className="text-sm">{row.original.source_kind}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'community',
|
||||
header: 'Community',
|
||||
cell: ({ row }: { row: { original: CdnSource } }) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{communityLabel(row.original.community_id, communities)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'last_refreshed_at',
|
||||
accessorFn: (row: CdnSource) => row.last_refreshed_at ?? '',
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataGridColumnHeader column={column as never} title="Обновлено" />
|
||||
),
|
||||
cell: ({ row }: { row: { original: CdnSource } }) => (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatDateTime(row.original.last_refreshed_at)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
enableSorting: false,
|
||||
header: () => null,
|
||||
cell: ({ row }: { row: { original: CdnSource } }) => (
|
||||
<RowActions
|
||||
onEdit={() => onEdit({ kind: 'cdn', entry: row.original })}
|
||||
onDelete={() => onDelete({ kind: 'cdn', entry: row.original })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<CdnSource>[]
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
accessorKey: 'asn',
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataGridColumnHeader column={column as never} title="ASN" />
|
||||
),
|
||||
cell: ({ row }: { row: { original: AsEntry } }) => (
|
||||
<span className="font-mono text-sm">{row.original.asn}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'asn_name',
|
||||
header: 'Имя',
|
||||
cell: ({ row }: { row: { original: AsEntry } }) => (
|
||||
<span className="text-sm">{row.original.asn_name ?? '—'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'prefix_count',
|
||||
header: 'Префиксов',
|
||||
cell: ({ row }: { row: { original: AsEntry } }) => (
|
||||
<span className="font-mono text-sm">{row.original.prefix_count ?? '—'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'community',
|
||||
header: 'Community',
|
||||
cell: ({ row }: { row: { original: AsEntry } }) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{communityLabel(row.original.community_id, communities)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
enableSorting: false,
|
||||
header: () => null,
|
||||
cell: ({ row }: { row: { original: AsEntry } }) => (
|
||||
<RowActions
|
||||
onEdit={() => onEdit({ kind: 'as', entry: row.original })}
|
||||
onDelete={() => onDelete({ kind: 'as', entry: row.original })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<AsEntry>[]
|
||||
}, [communities, mod.type, onDelete, onEdit])
|
||||
|
||||
type RowType = DomainEntry | IpRangeEntry | CdnSource | AsEntry
|
||||
const data = rows as unknown as RowType[]
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns: columns as ColumnDef<RowType>[],
|
||||
...createClientDataGridOptions<RowType>(),
|
||||
getRowId: (row) => row.id,
|
||||
})
|
||||
|
||||
return (
|
||||
<DataGridShell
|
||||
table={table}
|
||||
recordCount={data.length}
|
||||
isLoading={isLoading}
|
||||
emptyMessage="Нет записей"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export type { DeleteTarget as ModuleEntryDeleteTarget }
|
||||
Reference in New Issue
Block a user