From 063761539c240a9d1abf8708a018a53f8aeffe69 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 25 Jun 2026 17:27:39 +0700 Subject: [PATCH] refactor: Consolidate density-related logic by moving densityRowClass and densityLabel to a dedicated data-table-density component, improving code organization and reusability across data table components --- apps/web/src/components/data-table-density.ts | 15 ++++ .../web/src/components/data-table-toolbar.tsx | 24 ++----- apps/web/src/components/data-table-view.tsx | 7 +- .../web/src/components/domains-data-table.tsx | 2 +- .../src/components/expiring-certs-card.tsx | 72 +++++++++++-------- apps/web/src/components/subdomains-table.tsx | 2 +- apps/web/src/routes/_auth/certificates.tsx | 2 +- 7 files changed, 68 insertions(+), 56 deletions(-) create mode 100644 apps/web/src/components/data-table-density.ts diff --git a/apps/web/src/components/data-table-density.ts b/apps/web/src/components/data-table-density.ts new file mode 100644 index 0000000..0acf344 --- /dev/null +++ b/apps/web/src/components/data-table-density.ts @@ -0,0 +1,15 @@ +export type Density = 'compact' | 'comfortable' | 'spacious' + +export const densityRowClass: Record = { + compact: '[&_tr]:h-9 [&_tr_td]:py-1 [&_tr_th]:py-1 text-xs', + comfortable: '[&_tr]:h-10 [&_tr_td]:py-2 [&_tr_th]:py-2 text-sm', + spacious: '[&_tr]:h-12 [&_tr_td]:py-3 [&_tr_th]:py-3 text-sm', +} + +export const densityLabel: Record = { + compact: 'Компактная', + comfortable: 'Обычная', + spacious: 'Просторная', +} + +export const pageSizeOptions = [10, 20, 50] as const diff --git a/apps/web/src/components/data-table-toolbar.tsx b/apps/web/src/components/data-table-toolbar.tsx index de8df13..0f6f2a4 100644 --- a/apps/web/src/components/data-table-toolbar.tsx +++ b/apps/web/src/components/data-table-toolbar.tsx @@ -17,22 +17,11 @@ import { DropdownMenuTrigger, } from '@cfdm/ui/components/dropdown-menu' import { AppButton } from '@/components/app-button' - -type Density = 'compact' | 'comfortable' | 'spacious' - -const densityRowClass: Record = { - compact: '[&_tr]:h-9 [&_tr_td]:py-1 [&_tr_th]:py-1 text-xs', - comfortable: '[&_tr]:h-10 [&_tr_td]:py-2 [&_tr_th]:py-2 text-sm', - spacious: '[&_tr]:h-12 [&_tr_td]:py-3 [&_tr_th]:py-3 text-sm', -} - -const densityLabel: Record = { - compact: 'Компактная', - comfortable: 'Обычная', - spacious: 'Просторная', -} - -const pageSizeOptions = [10, 20, 50] as const +import { + densityLabel, + pageSizeOptions, + type Density, +} from '@/components/data-table-density' interface DataTableToolbarProps { table: Table @@ -137,6 +126,3 @@ export function DataTableToolbar({ ) } - -export type { Density } -export { densityRowClass } diff --git a/apps/web/src/components/data-table-view.tsx b/apps/web/src/components/data-table-view.tsx index 44e2bcf..02d2081 100644 --- a/apps/web/src/components/data-table-view.tsx +++ b/apps/web/src/components/data-table-view.tsx @@ -17,11 +17,8 @@ import { } from '@cfdm/ui/components/table' import { cn } from '@cfdm/ui/lib/utils' import { DataTablePagination } from '@/components/data-table-pagination' -import { - DataTableToolbar, - densityRowClass, - type Density, -} from '@/components/data-table-toolbar' +import { DataTableToolbar } from '@/components/data-table-toolbar' +import { densityRowClass, type Density } from '@/components/data-table-density' interface DataTableViewProps { table: TableType diff --git a/apps/web/src/components/domains-data-table.tsx b/apps/web/src/components/domains-data-table.tsx index f64be8a..8f0cd8e 100644 --- a/apps/web/src/components/domains-data-table.tsx +++ b/apps/web/src/components/domains-data-table.tsx @@ -16,7 +16,7 @@ import type { DomainListItem } from '@/lib/schemas' import { AppBadge } from '@/components/app-badge' import { AppButton } from '@/components/app-button' import { DataTableView } from '@/components/data-table-view' -import type { Density } from '@/components/data-table-toolbar' +import type { Density } from '@/components/data-table-density' import { DropdownMenu, DropdownMenuContent, diff --git a/apps/web/src/components/expiring-certs-card.tsx b/apps/web/src/components/expiring-certs-card.tsx index 28f847f..6e4b526 100644 --- a/apps/web/src/components/expiring-certs-card.tsx +++ b/apps/web/src/components/expiring-certs-card.tsx @@ -1,3 +1,4 @@ +import { useMemo, useState } from 'react' import { ShieldCheckIcon } from 'lucide-react' import type { Certificate } from '@/lib/schemas' import { formatRelative } from '@/lib/format' @@ -20,25 +21,43 @@ import { import { ScrollArea } from '@cfdm/ui/components/scroll-area' const WARN_DAYS = 14 +const DAY_MS = 1000 * 60 * 60 * 24 interface ExpiringCertsCardProps { certificates: Certificate[] limit?: number } -export function ExpiringCertsCard({ certificates, limit = 6 }: ExpiringCertsCardProps) { - const now = Date.now() - const upcoming = certificates +interface UpcomingEntry { + cert: Certificate + ts: number + expired: boolean +} + +function selectUpcoming( + certificates: Certificate[], + limit: number, + now: number, +): UpcomingEntry[] { + return certificates .filter((c) => c.expires_at) - .map((c) => ({ cert: c, ts: new Date(c.expires_at as string).getTime() })) + .map((c) => { + const ts = new Date(c.expires_at as string).getTime() + return { cert: c, ts, expired: ts < now } + }) .filter((entry) => Number.isNaN(entry.ts) === false) .sort((a, b) => a.ts - b.ts) - .filter((entry) => { - const days = (entry.ts - now) / (1000 * 60 * 60 * 24) - return days <= WARN_DAYS - }) + .filter((entry) => (entry.ts - now) / DAY_MS <= WARN_DAYS) .slice(0, limit) - .map((entry) => entry.cert) +} + +export function ExpiringCertsCard({ certificates, limit = 6 }: ExpiringCertsCardProps) { + const [now] = useState(() => Date.now()) + + const upcoming = useMemo( + () => selectUpcoming(certificates, limit, now), + [certificates, limit, now], + ) return ( @@ -58,26 +77,21 @@ export function ExpiringCertsCard({ certificates, limit = 6 }: ExpiringCertsCard ) : ( - {upcoming.map((cert) => { - const expired = new Date(cert.expires_at as string).getTime() < now - return ( - - -
- - {cert.hostname} - - - {formatRelative(cert.expires_at)} - -
- -
-
- ) - })} + {upcoming.map((entry) => ( + + +
+ + {entry.cert.hostname} + + + {formatRelative(entry.cert.expires_at)} + +
+ +
+
+ ))}
)} diff --git a/apps/web/src/components/subdomains-table.tsx b/apps/web/src/components/subdomains-table.tsx index 3bc79f3..1670f70 100644 --- a/apps/web/src/components/subdomains-table.tsx +++ b/apps/web/src/components/subdomains-table.tsx @@ -12,7 +12,7 @@ import { import { MoreHorizontalIcon } from 'lucide-react' import { ConfirmDialog } from '@/components/confirm-dialog' import { DataTableView } from '@/components/data-table-view' -import type { Density } from '@/components/data-table-toolbar' +import type { Density } from '@/components/data-table-density' import type { SubdomainTableRow } from '@/hooks/use-domain-page' import { formatSubdomainServiceLinks } from '@/hooks/use-domain-page' import { formatDate } from '@/lib/format' diff --git a/apps/web/src/routes/_auth/certificates.tsx b/apps/web/src/routes/_auth/certificates.tsx index 50c4082..2e7ac55 100644 --- a/apps/web/src/routes/_auth/certificates.tsx +++ b/apps/web/src/routes/_auth/certificates.tsx @@ -24,7 +24,7 @@ import { EmptyState } from '@/components/empty-state' import { StatusBadge } from '@/components/status-badge' import { DataTableCard } from '@/components/data-table-card' import { DataTableView } from '@/components/data-table-view' -import type { Density } from '@/components/data-table-toolbar' +import type { Density } from '@/components/data-table-density' import { ChartCard } from '@/components/chart-card' import { AppButton } from '@/components/app-button' import {