From 6ceb7ff9c741701ac0afbbb3d2a324cddb5d484e Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 17 Jul 2026 00:59:07 +0700 Subject: [PATCH] feat(web): enhance UI components and improve documentation - Updated frontend UI patterns documentation to reflect new components and their usage. - Enhanced CertKpiCards for better KPI visualization and streamlined data handling. - Refactored OpsDashboard and ResourcePage for improved layout consistency and loading states. - Introduced CatalogBoardToggle for better navigation between views in the Groups and Services pages. - Improved Tabs component styles for better responsiveness and accessibility. Co-authored-by: Cursor --- .cursor/rules/frontend-ui-patterns.mdc | 55 +-- .../blocks/card-35/components/card-grid.tsx | 17 + .../blocks/card-35/components/card-item.tsx | 233 ++++++++++++ .../blocks/card-35/components/data.tsx | 23 ++ .../src/components/blocks/card-35/page.tsx | 9 + .../src/components/catalog-board-toggle.tsx | 33 ++ apps/web/src/components/cert-kpi-cards.tsx | 42 +-- .../src/components/columns/groups-columns.tsx | 165 +++++++++ .../components/columns/services-columns.tsx | 175 +++++++++ apps/web/src/components/examples/c-tabs-2.tsx | 129 +++++++ apps/web/src/components/reui-kit/index.ts | 2 +- .../src/components/reui-kit/kpi-stat-grid.tsx | 127 +++---- .../src/components/reui-kit/ops-dashboard.tsx | 52 +-- .../src/components/reui-kit/resource-page.tsx | 16 +- .../web/src/components/service-edit-sheet.tsx | 12 +- apps/web/src/routes/_auth/domains/index.tsx | 2 +- apps/web/src/routes/_auth/groups.tsx | 169 +++++++-- apps/web/src/routes/_auth/index.tsx | 132 +++---- apps/web/src/routes/_auth/services.tsx | 344 +++++++++++++----- packages/ui/src/components/tabs.tsx | 16 +- 20 files changed, 1372 insertions(+), 381 deletions(-) create mode 100644 apps/web/src/components/blocks/card-35/components/card-grid.tsx create mode 100644 apps/web/src/components/blocks/card-35/components/card-item.tsx create mode 100644 apps/web/src/components/blocks/card-35/components/data.tsx create mode 100644 apps/web/src/components/blocks/card-35/page.tsx create mode 100644 apps/web/src/components/catalog-board-toggle.tsx create mode 100644 apps/web/src/components/columns/groups-columns.tsx create mode 100644 apps/web/src/components/columns/services-columns.tsx create mode 100644 apps/web/src/components/examples/c-tabs-2.tsx diff --git a/.cursor/rules/frontend-ui-patterns.mdc b/.cursor/rules/frontend-ui-patterns.mdc index c86ef4f..7b8a429 100644 --- a/.cursor/rules/frontend-ui-patterns.mdc +++ b/.cursor/rules/frontend-ui-patterns.mdc @@ -35,7 +35,8 @@ apps/web/src/components/ ← shared + domain + layout kanban-board.tsx ← kanban + KanbanBoardSkeleton detail-panel.tsx ← detail: Frame header/metrics settings-shell.tsx - ops-dashboard.tsx + ops-dashboard.tsx ← KPI card-35 + charts + catalog-board-toggle.tsx empty-state.tsx query-state.tsx confirm-dialog.tsx @@ -52,11 +53,12 @@ apps/web/src/components/ ← shared + domain + layout | Элемент | Shared | Primitive | |---------|--------|-----------| | Page wrapper | `PageShell` | — | -| List page | `ResourcePage` | ReUI `Frame` + `data-grid` + `filters` + shadcn `Tabs` | +| List page | `ResourcePage` | ReUI `Frame` + `data-grid` + `filters` + shadcn `Tabs` `variant="line"` | +| Catalog / Board | `CatalogBoardToggle` + `ResourcePage` / `KanbanBoard` | `?view=board` на `/groups`, `/services` | | Kanban | `KanbanBoard` / `KanbanBoardSkeleton` | ReUI `kanban` + `Frame` | | Detail | `DetailPanel` | ReUI `Frame` | | Settings | `SettingsShell` | — | -| Dashboard | `OpsDashboard` / `KpiStatGrid` | ReUI `Frame` | +| Dashboard KPI | `OpsDashboard` / `KpiStatGrid` | ReUI Frame [card-35](https://reui.io/preview/base/card-35) | | Empty | `EmptyState` | `Empty` | | Loading / Error | `QueryState` / kit skeletons | `Skeleton`, `Alert` | | Status | `StatusBadge` | ReUI `Badge` (`success`/`info`/`warning`) | @@ -76,32 +78,41 @@ apps/web/src/components/ ← shared + domain + layout Max 1 primary (`default`) на экран; остальные `outline` / `ghost`. -## Line tabs (ResourcePage) +Toggle «Доска» / «К каталогу» — всегда `outline` в `primaryAction` / `KanbanBoard.toolbarActions` (не отдельный Frame-shell). -Эталон: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2). +## Line tabs (project standard) + +Эталон: [c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) + counted [filtering-2](https://reui.io/preview/base/data-grid-filtering-2). ```tsx - - + + Label {count} ``` -- Active = яркий текст + **белый underline** (`after:`), без фона/инвертированного badge -- Count pill всегда `bg-muted` (как в preview) -- `h-auto` / `flex-none` / `data-active:bg-transparent` — снять клип `h-8` и `dark:bg-input/30` у примитива +- Примитив: [`packages/ui/.../tabs.tsx`](../../packages/ui/src/components/tabs.tsx) — line = `h-auto`, без `flex-1`, без `dark:data-active:bg-input/30`, underline `after:bottom-0` +- Active = яркий текст + **foreground underline**, без фона +- Count pill всегда `bg-muted` - Active state — Base UI `data-active`, не Radix `data-[state=active]` +- Не трогать internals `reui/date-selector` + +## Catalog / Board (`/groups`, `/services`) + +| Режим | UI | Search | +|-------|-----|--------| +| Catalog (default) | `ResourcePage` + primary create | omit / `view=catalog` | +| Board | `KanbanBoard` DnD | `?view=board` | + +- Groups tabs: Все / С доменами / Пустые +- Services tabs: Все / Включены / Выключены / Без группы +- DnD только на board; kanban hooks/cards не удалять + +## Dashboard KPI + +Эталон: [card-35](https://reui.io/preview/base/card-35) — muted **label** + крупный **value** + optional **hint**; клик по карточке через `to` (`Link`), без отдельных hint-links. ## Sidebar @@ -122,6 +133,8 @@ Max 1 primary (`default`) на экран; остальные `outline` / `ghost |------|----------------| | Shell | [app-shell-12](https://reui.io/preview/base/app-shell-12) | | List + tabs + filters | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) | +| Line tabs | [c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) | +| KPI | [card-35](https://reui.io/preview/base/card-35) | | Login | auth-13 / login-03 | ## Spacing @@ -130,7 +143,7 @@ Max 1 primary (`default`) на экран; остальные `outline` / `ghost AppShell main: gap-4 md:gap-6, px-4 md:px-6, py-4 md:py-5 PageShell: gap-4 md:gap-6 Title/desc: gap-px -Card/Frame grid: gap-4 +Card/Frame grid: gap-4 (dashboard denser: gap-2 md:gap-3) FieldGroup: gap-4 Item list: gap-2 Toolbar / header actions: gap-2 @@ -143,7 +156,7 @@ Tabs list (line): gap-5 Каждый блок: **default, hover, focus, disabled, empty, loading, error**. -- **Loading** — `Skeleton` / `ResourcePage` skeleton / `KanbanBoardSkeleton`, не Spinner на странице +- **Loading** — `Skeleton` / `ResourcePage` skeleton / `KanbanBoardSkeleton` / `OpsDashboard` skeleton, не Spinner на странице - **Empty** — `EmptyState` с CTA - **Zero-results** — message внутри DataGrid (+ «Сбросить») - **Error** — `QueryState` / `Alert` + `onRetry` diff --git a/apps/web/src/components/blocks/card-35/components/card-grid.tsx b/apps/web/src/components/blocks/card-35/components/card-grid.tsx new file mode 100644 index 0000000..15746d6 --- /dev/null +++ b/apps/web/src/components/blocks/card-35/components/card-grid.tsx @@ -0,0 +1,17 @@ +import { Frame } from "@/components/reui/frame" + +import { CardItem } from "./card-item" +import { CARDS } from "./data" + +export function CardGrid() { + return ( + + {/* Grid */} +
+ {CARDS.map((card) => ( + + ))} +
+ + ) +} \ No newline at end of file diff --git a/apps/web/src/components/blocks/card-35/components/card-item.tsx b/apps/web/src/components/blocks/card-35/components/card-item.tsx new file mode 100644 index 0000000..9120ced --- /dev/null +++ b/apps/web/src/components/blocks/card-35/components/card-item.tsx @@ -0,0 +1,233 @@ +import { useId } from "react" +import { FramePanel } from "@/components/reui/frame" + +import { ICard } from "./data" + +function CardDotPatternRtl() { + const id = useId().replace(/:/g, "") + const width = 240 + const height = 136 + const patternId = `${id}-pattern` + const maskId = `${id}-mask` + const gradientId = `${id}-gradient` + + return ( + + ) +} +export function CardItem({ card }: { card: ICard }) { + return ( + + {/* Card */} + +
+ + {card.title} + + + {card.total} + +
+
+ ) +} \ No newline at end of file diff --git a/apps/web/src/components/blocks/card-35/components/data.tsx b/apps/web/src/components/blocks/card-35/components/data.tsx new file mode 100644 index 0000000..85cd589 --- /dev/null +++ b/apps/web/src/components/blocks/card-35/components/data.tsx @@ -0,0 +1,23 @@ +export interface ICard { + title: string + total: string +} + +export const CARDS: ICard[] = [ + { + title: "Avg. Deal Size", + total: "$14,9M", + }, + { + title: "Due Deals", + total: "16", + }, + { + title: "Pending Invoices", + total: "$2.7M", + }, + { + title: "Active Clients", + total: "36", + }, +] \ No newline at end of file diff --git a/apps/web/src/components/blocks/card-35/page.tsx b/apps/web/src/components/blocks/card-35/page.tsx new file mode 100644 index 0000000..7ea6d32 --- /dev/null +++ b/apps/web/src/components/blocks/card-35/page.tsx @@ -0,0 +1,9 @@ +import { CardGrid } from "./components/card-grid" + +export function Page() { + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/apps/web/src/components/catalog-board-toggle.tsx b/apps/web/src/components/catalog-board-toggle.tsx new file mode 100644 index 0000000..b071b32 --- /dev/null +++ b/apps/web/src/components/catalog-board-toggle.tsx @@ -0,0 +1,33 @@ +import { Button } from '@cfdm/ui/components/button' + +interface CatalogBoardToggleProps { + view: 'catalog' | 'board' + onViewChange: (view: 'catalog' | 'board') => void +} + +export function CatalogBoardToggle({ + view, + onViewChange, +}: CatalogBoardToggleProps) { + if (view === 'board') { + return ( + + ) + } + + return ( + + ) +} diff --git a/apps/web/src/components/cert-kpi-cards.tsx b/apps/web/src/components/cert-kpi-cards.tsx index 0b3f7cc..0967bbe 100644 --- a/apps/web/src/components/cert-kpi-cards.tsx +++ b/apps/web/src/components/cert-kpi-cards.tsx @@ -1,15 +1,7 @@ import { useMemo } from 'react' -import { - AlertTriangleIcon, - CheckCircle2Icon, - ShieldAlertIcon, - ShieldCheckIcon, -} from 'lucide-react' -import { Badge } from '@/components/reui/badge' +import { ShieldCheckIcon } from 'lucide-react' import { KpiStatGrid, type KpiStatCard } from '@/components/reui-kit/kpi-stat-grid' -const KPI_ICON_BG = ['bg-chart-1', 'bg-chart-2', 'bg-chart-3', 'bg-chart-4'] as const - const ACTIVE_STATUSES = new Set(['active', 'ok', 'synced']) const PROBLEM_STATUSES = new Set(['expired', 'error', 'conflict']) @@ -52,49 +44,33 @@ export function CertKpiCards({ return [ { id: 'all', - typeLabel: 'Мониторинг', - title: 'Всего', - metricLabel: 'Проверенные хосты', + label: 'Всего', value: allCount, - icon: