feat(design-preview): перетаскивание строк в «Ссылках приложений» через dnd-kit
quality / changes (push) Successful in 9s
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m32s
quality / api (push) Successful in 42s
CD / quality (push) Successful in 2m25s
CD / publish (push) Successful in 2m52s

PreviewSectionGrid получил onReorder: ручка DataGridTableDndRowHandle и DataGridTableDndRows
из ReUI. Порядок меняется перетаскиванием, меню строки осталось запасным путём.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
DenozordecandClaude Sonnet 5.5 committed 2026-10-18 02:03:05 +07:00
1 parent 4f5026980a
commit 41f5669812
2 files changed
+50 -9

No files matched your search

@@ -4,7 +4,6 @@ import {
CloudIcon,
EyeIcon,
FingerprintIcon,
GripVerticalIcon,
KeyRoundIcon,
LayoutDashboardIcon,
LayoutGridIcon,
@@ -141,6 +140,15 @@ export function AppLinksPage() {
[addChange],
)
/** Перетаскивание: строка встаёт на место той, на которую её бросили. */
const dropOn = useCallback(
(activeId: string, overId: string) => {
const to = rowsRef.current.findIndex((r) => r.id === overId)
if (to >= 0) moveTo(activeId as AppId, () => to)
},
[moveTo],
)
const resetOrder = useCallback(() => {
const before = rowsRef.current
if (before.every((r, i) => r.id === INITIAL[i]?.id)) return
@@ -158,10 +166,9 @@ export function AppLinksPage() {
{
id: 'order',
title: '№',
size: 76,
size: 52,
cell: (r) => (
<span className="text-muted-foreground flex items-center gap-1 text-xs tabular-nums">
<GripVerticalIcon className="size-4" aria-hidden />
<span className="text-muted-foreground text-sm tabular-nums">
{rows.findIndex((x) => x.id === r.id) + 1}
</span>
),
@@ -267,7 +274,7 @@ export function AppLinksPage() {
<TabsContent value="list" className="min-w-0">
<PreviewSectionGrid
title="Ссылки сервисов"
description="Порядок строк = порядок в меню · изменения применяются после сохранения"
description="Перетащите строку за ручку: порядок строк = порядок в меню · изменения применяются после сохранения"
rows={rows}
columns={columns}
getRowId={(r) => r.id}
@@ -296,6 +303,7 @@ export function AppLinksPage() {
</Button>
</>
)}
onReorder={dropOn}
onRowClick={setEditing}
/>
</TabsContent>
+37 -4
View File
@@ -6,6 +6,11 @@ import { DataGrid, dataGridFeatures, type DataGridFeatures } from '@/components/
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
import { DataGridPagination } from '@/components/reui/data-grid/data-grid-pagination'
import { DataGridScrollArea } from '@/components/reui/data-grid/data-grid-scroll-area'
import {
DataGridTableDndRowHandle,
DataGridTableDndRows,
} from '@/components/reui/data-grid/data-grid-table-dnd-rows'
import type { DragEndEvent } from '@dnd-kit/core'
import {
DataGridTable,
DataGridTableRowSelect,
@@ -40,6 +45,7 @@ export type GridColumn<T> = {
export type GridView<T> = { label: string; match: (row: T) => boolean }
const SELECT_COLUMN_SIZE = 44
const DRAG_COLUMN_SIZE = 44
/**
* Секция-таблица макета: Frame + Filters-подобная панель + DataGrid на DataGridScrollArea.
@@ -59,6 +65,7 @@ export function PreviewSectionGrid<T extends object>({
selectable = false,
bulkActions,
toolbar,
onReorder,
pageSize = 8,
emptyMessage = 'Нет записей по текущим фильтрам.',
className,
@@ -75,6 +82,8 @@ export function PreviewSectionGrid<T extends object>({
selectable?: boolean
bulkActions?: (selectedIds: string[], clear: () => void) => ReactNode
toolbar?: ReactNode
/** Включает перетаскивание строк (dnd-kit). Передаёт id строки и id строки, на место которой её бросили. */
onReorder?: (activeId: string, overId: string) => void
pageSize?: number
emptyMessage?: string
className?: string
@@ -113,6 +122,16 @@ export function PreviewSectionGrid<T extends object>({
const defs = useMemo<ColumnDef<DataGridFeatures, T>[]>(() => {
const list: ColumnDef<DataGridFeatures, T>[] = []
if (onReorder) {
list.push({
id: 'drag',
header: () => <span className="sr-only">Порядок</span>,
cell: () => <DataGridTableDndRowHandle />,
enableSorting: false,
enableResizing: false,
size: DRAG_COLUMN_SIZE,
})
}
if (selectable) {
list.push({
id: 'select',
@@ -140,7 +159,7 @@ export function PreviewSectionGrid<T extends object>({
})
}
return list
}, [columns, selectable])
}, [columns, selectable, onReorder])
const table = useTable({
features: dataGridFeatures,
@@ -154,7 +173,17 @@ export function PreviewSectionGrid<T extends object>({
onPaginationChange: setPagination,
})
const minWidth = columns.reduce((sum, c) => sum + c.size, selectable ? SELECT_COLUMN_SIZE : 0)
const dataIds = table.getRowModel().rows.map((r) => r.id)
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
if (!onReorder || !active || !over || active.id === over.id) return
onReorder(String(active.id), String(over.id))
}
const minWidth = columns.reduce(
(sum, c) => sum + c.size,
(selectable ? SELECT_COLUMN_SIZE : 0) + (onReorder ? DRAG_COLUMN_SIZE : 0),
)
const selectedIds = table.getSelectedRowModel().rows.map((r) => r.id)
const paged = data.length > pageSize
@@ -165,7 +194,7 @@ export function PreviewSectionGrid<T extends object>({
recordCount={data.length}
onRowClick={onRowClick}
emptyMessage={emptyMessage}
tableLayout={{ headerSticky: false, dense: true, width: 'auto' }}
tableLayout={{ headerSticky: false, dense: true, width: 'auto', rowsDraggable: Boolean(onReorder) }}
>
<Frame spacing="sm" className={cn('w-full min-w-0', className)}>
<FrameHeader>
@@ -226,7 +255,11 @@ export function PreviewSectionGrid<T extends object>({
) : null}
<DataGridScrollArea>
<div className="pb-3" style={{ minWidth }}>
<DataGridTable />
{onReorder ? (
<DataGridTableDndRows dataIds={dataIds} handleDragEnd={handleDragEnd} />
) : (
<DataGridTable />
)}
</div>
</DataGridScrollArea>
<Separator />