2025-10-28 11:21:04 -04:00
|
|
|
"use client"
|
|
|
|
|
|
|
|
|
|
import * as React from "react"
|
|
|
|
|
|
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
|
|
feat(admin): DB-backed integration toggles + sticky cols + dark contrast
Builds on the env-var INTEGRATIONS_DISABLED shipped with the nav-design
overhaul. Adds a DB-backed admin UI so operators can flip integrations
without editing .env and restarting the container, plus the remaining
visual cleanup items from the design backlog.
Integration toggles
- Migration 081 — integration_settings table (key PK, disabled flag,
reason, disabled_by audit, disabled_at). Seeded with all 13 known
integrations as enabled.
- GET / PATCH /api/admin/integrations — gated by requirePermission
(admin, access). PATCH clears the in-process integration-health
cache so toggles take effect within seconds.
- /admin/integrations admin page with a Switch per integration, optional
reason input, audit-info subtitle (disabled by, when, why), live
status light from /api/dashboard/integration-health.
- integration-health service merges env-var disable list with DB rows;
degrades gracefully if migration unapplied / DB unreachable.
- Wired into the Admin nav dropdown (eight items now).
- CLAUDE.md describes both env + DB sources.
Sticky first column on tables
- Table primitive accepts stickyFirstColumn?: boolean. When true, TH
and TD :first-child stay pinned during horizontal scroll, with
background inheritance preserving hover and selected row tints.
- DataTable exposes the prop too — on by default for paginated tables.
- /addigy-devices opts in.
Dark-mode contrast
- --border lifted from 10% to 14% in .dark; --input from 15% to 18%;
--sidebar-border to 14%.
- StatusLight outline ring lifted from /10 to /15 (light) and /20 (dark).
- DetailModal empty-cell em-dash lifted from /40 to /70 so missing
values are legible on dark surfaces.
DESIGN.md
- Closed sticky-first-column, dark-mode contrast, and palette-audit
items (palette deprioritized — most uses are semantic).
- Skeleton helpers documented as preferred for new code; existing
ad-hoc patterns left in place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:55:22 -04:00
|
|
|
interface TableProps extends React.ComponentProps<"table"> {
|
|
|
|
|
/** Pin the first column when the table scrolls horizontally. Useful on
|
|
|
|
|
* wide list tables where the first column is an identifier. */
|
|
|
|
|
stickyFirstColumn?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Table({ className, stickyFirstColumn, ...props }: TableProps) {
|
2025-10-28 11:21:04 -04:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-slot="table-container"
|
|
|
|
|
className="relative w-full overflow-x-auto"
|
|
|
|
|
>
|
|
|
|
|
<table
|
|
|
|
|
data-slot="table"
|
feat(admin): DB-backed integration toggles + sticky cols + dark contrast
Builds on the env-var INTEGRATIONS_DISABLED shipped with the nav-design
overhaul. Adds a DB-backed admin UI so operators can flip integrations
without editing .env and restarting the container, plus the remaining
visual cleanup items from the design backlog.
Integration toggles
- Migration 081 — integration_settings table (key PK, disabled flag,
reason, disabled_by audit, disabled_at). Seeded with all 13 known
integrations as enabled.
- GET / PATCH /api/admin/integrations — gated by requirePermission
(admin, access). PATCH clears the in-process integration-health
cache so toggles take effect within seconds.
- /admin/integrations admin page with a Switch per integration, optional
reason input, audit-info subtitle (disabled by, when, why), live
status light from /api/dashboard/integration-health.
- integration-health service merges env-var disable list with DB rows;
degrades gracefully if migration unapplied / DB unreachable.
- Wired into the Admin nav dropdown (eight items now).
- CLAUDE.md describes both env + DB sources.
Sticky first column on tables
- Table primitive accepts stickyFirstColumn?: boolean. When true, TH
and TD :first-child stay pinned during horizontal scroll, with
background inheritance preserving hover and selected row tints.
- DataTable exposes the prop too — on by default for paginated tables.
- /addigy-devices opts in.
Dark-mode contrast
- --border lifted from 10% to 14% in .dark; --input from 15% to 18%;
--sidebar-border to 14%.
- StatusLight outline ring lifted from /10 to /15 (light) and /20 (dark).
- DetailModal empty-cell em-dash lifted from /40 to /70 so missing
values are legible on dark surfaces.
DESIGN.md
- Closed sticky-first-column, dark-mode contrast, and palette-audit
items (palette deprioritized — most uses are semantic).
- Skeleton helpers documented as preferred for new code; existing
ad-hoc patterns left in place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:55:22 -04:00
|
|
|
className={cn(
|
|
|
|
|
"w-full caption-bottom text-sm",
|
|
|
|
|
stickyFirstColumn && [
|
|
|
|
|
// Header cell: sticky against the muted header bg.
|
|
|
|
|
"[&_thead_tr_th:first-child]:sticky",
|
|
|
|
|
"[&_thead_tr_th:first-child]:left-0",
|
|
|
|
|
"[&_thead_tr_th:first-child]:z-20",
|
|
|
|
|
"[&_thead_tr_th:first-child]:bg-muted",
|
|
|
|
|
// Body cell: sticky, inherits the row's background so hover +
|
|
|
|
|
// selected states still read; falls back to card surface.
|
|
|
|
|
"[&_tbody_tr_td:first-child]:sticky",
|
|
|
|
|
"[&_tbody_tr_td:first-child]:left-0",
|
|
|
|
|
"[&_tbody_tr_td:first-child]:z-10",
|
|
|
|
|
"[&_tbody_tr_td:first-child]:bg-card",
|
|
|
|
|
// When the parent row has its hover bg, override.
|
|
|
|
|
"[&_tbody_tr:hover_td:first-child]:bg-muted/50",
|
|
|
|
|
"[&_tbody_tr[data-state=selected]_td:first-child]:bg-muted",
|
|
|
|
|
],
|
|
|
|
|
className,
|
|
|
|
|
)}
|
2025-10-28 11:21:04 -04:00
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
|
|
|
return (
|
|
|
|
|
<thead
|
|
|
|
|
data-slot="table-header"
|
|
|
|
|
className={cn("[&_tr]:border-b", className)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
|
|
|
return (
|
|
|
|
|
<tbody
|
|
|
|
|
data-slot="table-body"
|
|
|
|
|
className={cn("[&_tr:last-child]:border-0", className)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
|
|
|
return (
|
|
|
|
|
<tfoot
|
|
|
|
|
data-slot="table-footer"
|
|
|
|
|
className={cn(
|
|
|
|
|
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
|
|
|
return (
|
|
|
|
|
<tr
|
|
|
|
|
data-slot="table-row"
|
|
|
|
|
className={cn(
|
|
|
|
|
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
|
|
|
return (
|
|
|
|
|
<th
|
|
|
|
|
data-slot="table-head"
|
|
|
|
|
className={cn(
|
|
|
|
|
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
|
|
|
return (
|
|
|
|
|
<td
|
|
|
|
|
data-slot="table-cell"
|
|
|
|
|
className={cn(
|
|
|
|
|
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TableCaption({
|
|
|
|
|
className,
|
|
|
|
|
...props
|
|
|
|
|
}: React.ComponentProps<"caption">) {
|
|
|
|
|
return (
|
|
|
|
|
<caption
|
|
|
|
|
data-slot="table-caption"
|
|
|
|
|
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export {
|
|
|
|
|
Table,
|
|
|
|
|
TableHeader,
|
|
|
|
|
TableBody,
|
|
|
|
|
TableFooter,
|
|
|
|
|
TableHead,
|
|
|
|
|
TableRow,
|
|
|
|
|
TableCell,
|
|
|
|
|
TableCaption,
|
|
|
|
|
}
|