Files
MikrotikManager/components/data-grids/data-collection-scheduler-data-grid.tsx
DenozordecandCursor 5e0c16e808
Docker images / prepare-release (push) Successful in 8s
Docker images / backend-image (push) Successful in 1m31s
Docker images / frontend-image (push) Successful in 2m11s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 40s
Docker images / publish-release (push) Successful in 10s
fix(ui): выровнять confirms и токены под ReUI PRO
Добавить ui-design-contract, AlertDialog вместо hand-roll, CodeExportSheet для preview фильтров, ReUI Badge и gap вместо space-y.

Co-authored-by: Cursor <[email protected]>
2026-09-06 00:12:57 +07:00

220 lines
7.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useMemo } from "react"
import {
type ColumnDef,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import type { SchedulerJobStatusDto } from "@/lib/scheduler-settings"
import { FormToggle } from "@/components/form-kit"
import { Badge } from "@/components/reui/badge"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell"
import {
DATA_GRID_CELL_PAD,
DATA_GRID_CELL_PAD_FIRST,
DATA_GRID_CELL_PAD_LAST,
} from "@/components/data-grids/shared/data-grid-layout"
import { cn } from "@/lib/utils"
import { RefreshCwIcon } from "lucide-react"
export interface SchedulerJobGridRow {
id: string
jobKey: string
label: string
description: string
fixedSchedule: boolean
enabled: boolean
intervalValue: string
intervalReadOnly: boolean
intervalDisabled: boolean
defaultInterval: number
job?: SchedulerJobStatusDto
onEnabledChange?: (enabled: boolean) => void
onIntervalChange: (value: string) => void
onRunNow: () => void
runNowLoading: boolean
saveBusy: boolean
}
interface DataCollectionSchedulerDataGridProps {
rows: SchedulerJobGridRow[]
}
function DataCollectionSchedulerDataGrid({ rows }: DataCollectionSchedulerDataGridProps) {
const columns = useMemo<ColumnDef<SchedulerJobGridRow>[]>(
() => [
{
id: "task",
accessorKey: "label",
header: () => <span className="text-xs font-medium text-muted-foreground">Задача</span>,
enableSorting: false,
cell: ({ row }) => (
<div className="align-top">
<span className="font-medium text-sm">{row.original.label}</span>
<p className="text-[11px] text-muted-foreground mt-0.5 leading-snug">
{row.original.description}
</p>
<p className="text-[11px] text-muted-foreground font-mono mt-1">{row.original.jobKey}</p>
</div>
),
meta: {
headerClassName: DATA_GRID_CELL_PAD_FIRST,
cellClassName: cn(DATA_GRID_CELL_PAD_FIRST, "align-top"),
},
},
{
id: "enabled",
accessorKey: "enabled",
header: () => (
<span className="text-xs font-medium text-muted-foreground text-center block">Вкл</span>
),
enableSorting: false,
cell: ({ row }) => {
const { fixedSchedule, enabled, saveBusy, onEnabledChange } = row.original
return (
<div className="text-center align-top">
<span className={fixedSchedule ? "inline-flex pointer-events-none opacity-50" : "inline-flex"}>
<FormToggle
checked={enabled}
disabled={fixedSchedule || saveBusy}
onChange={(v) => {
if (fixedSchedule || saveBusy) return
onEnabledChange?.(v)
}}
/>
</span>
</div>
)
},
size: 56,
meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: cn(DATA_GRID_CELL_PAD, "align-top") },
},
{
id: "interval",
accessorKey: "intervalValue",
header: () => <span className="text-xs font-medium text-muted-foreground">Интервал (с)</span>,
enableSorting: false,
cell: ({ row }) => {
const r = row.original
return (
<div className="w-28 align-top">
<Input
value={r.intervalValue}
onChange={(e) => r.onIntervalChange(e.target.value)}
className="h-8 text-sm tabular-nums"
inputMode="numeric"
readOnly={r.intervalReadOnly}
disabled={r.intervalDisabled}
placeholder={String(r.defaultInterval)}
/>
</div>
)
},
meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: cn(DATA_GRID_CELL_PAD, "align-top") },
},
{
id: "lastRun",
accessorFn: (row) => row.job?.lastFinishedAt ?? "",
header: () => <span className="text-xs font-medium text-muted-foreground">Последний прогон</span>,
enableSorting: false,
cell: ({ row }) => {
const j = row.original.job
return (
<div className="text-xs text-muted-foreground align-top">
{j?.lastFinishedAt ? new Date(j.lastFinishedAt).toLocaleString("ru-RU") : "—"}
{j?.lastDurationMs != null && (
<span className="block text-[11px]">{j.lastDurationMs} мс</span>
)}
</div>
)
},
meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: cn(DATA_GRID_CELL_PAD, "align-top") },
},
{
id: "status",
accessorFn: (row) => row.job?.lastStatus ?? "",
header: () => <span className="text-xs font-medium text-muted-foreground">Статус</span>,
enableSorting: false,
cell: ({ row }) => {
const j = row.original.job
return (
<div className="flex flex-wrap items-center gap-1.5 align-top">
{j?.running ? (
<Badge variant="secondary" className="text-[10px]">
выполняется
</Badge>
) : null}
{j?.lastStatus ? (
<Badge
variant="outline"
className={cn(
"text-[10px]",
j.lastStatus === "ok" && "border-emerald-500/40 text-emerald-700 dark:text-emerald-400",
j.lastStatus === "error" && "border-destructive/50 text-destructive",
)}
>
{j.lastStatus}
</Badge>
) : null}
{j?.lastError ? (
<span className="text-[10px] text-destructive max-w-[200px] truncate block" title={j.lastError}>
{j.lastError}
</span>
) : null}
</div>
)
},
meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: cn(DATA_GRID_CELL_PAD, "align-top") },
},
{
id: "runNow",
header: () => (
<span className="text-xs font-medium text-muted-foreground text-right block">Сейчас</span>
),
enableSorting: false,
cell: ({ row }) => {
const r = row.original
return (
<div className="text-right align-top">
<Button
size="sm"
variant="outline"
className="h-8"
disabled={r.job?.running || r.runNowLoading}
onClick={r.onRunNow}
>
<RefreshCwIcon className={cn("size-3.5", r.runNowLoading && "animate-spin")} />
</Button>
</div>
)
},
meta: {
headerClassName: DATA_GRID_CELL_PAD_LAST,
cellClassName: cn(DATA_GRID_CELL_PAD_LAST, "align-top"),
},
},
],
[],
)
const table = useReactTable({
data: rows,
columns,
getCoreRowModel: getCoreRowModel(),
getRowId: (row) => row.id,
})
return (
<DataGridShell
table={table}
recordCount={rows.length}
tableClassNames={{ bodyRow: "group/row hover:bg-muted/40 text-sm" }}
/>
)
}
export { DataCollectionSchedulerDataGrid, type DataCollectionSchedulerDataGridProps }