From d1a21a79b215478c04a045212cdcd29e09920092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Thu, 9 Apr 2026 22:37:43 +0200 Subject: [PATCH] fix(ui): comprehensive dark-theme hardcoded color pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 — globals.css: add ~45 new dark-mode override rules covering 250+ component instances at once: - bg-*-50 (red/green/blue/yellow/amber/purple/indigo/orange/brand/emerald) - border-*-200 (colored alert/badge borders) - hover:bg-*-50/100 (colored hover states) - text-amber-700/orange-600/green-600/emerald-700/brand-700 (missing overrides) - divide-gray-50 (ChargeabilityWidget sticky section dividers) Phase 2 — targeted component fixes: - Button.tsx: add dark variants to secondary (bg-gray-800) and ghost variants - DynamicFieldEditor.tsx: add dark variants to INPUT_NORMAL and INPUT_ERROR constants - WidgetContainer.tsx: replace slate-900 (blue-tinted) gradient with neutral surface-card values (rgb 22,23,26 / 16,17,19) - status-styles.ts: add explicit dark variants to PROJECT_STATUS_BADGE and ORDER_TYPE_BADGE (consistent with other badge maps in same file) Phase 3 — dashboard widget tables: - TopValueWidget: dark thead, tbody divider, row hover - DemandWidget: dark thead, tbody divider, row hover - ChargeabilityWidget: dark sticky h3 headers (bg-white→surface-card), border-gray-100 thead rows, divide-gray-50 tbodys Co-Authored-By: Claude Sonnet 4.6 --- apps/web/src/app/globals.css | 39 +++++++++++++++++++ .../components/dashboard/WidgetContainer.tsx | 2 +- .../dashboard/widgets/ChargeabilityWidget.tsx | 12 +++--- .../dashboard/widgets/DemandWidget.tsx | 6 +-- .../dashboard/widgets/TopValueWidget.tsx | 6 +-- .../dynamic-fields/DynamicFieldEditor.tsx | 4 +- apps/web/src/components/ui/Button.tsx | 4 +- apps/web/src/lib/status-styles.ts | 18 ++++----- 8 files changed, 65 insertions(+), 26 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 7ae66eb..19e95bf 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -345,6 +345,45 @@ color: rgb(196 181 253) !important; } +/* Status/alert -50 backgrounds — covers ~250+ instances across the codebase */ +.dark .bg-red-50 { background-color: rgb(127 29 29 / 0.15) !important; } +.dark .bg-green-50 { background-color: rgb(6 78 59 / 0.15) !important; } +.dark .bg-blue-50 { background-color: rgb(30 58 138 / 0.15) !important; } +.dark .bg-yellow-50 { background-color: rgb(120 53 15 / 0.15) !important; } +.dark .bg-amber-50 { background-color: rgb(120 53 15 / 0.15) !important; } +.dark .bg-purple-50 { background-color: rgb(76 29 149 / 0.15) !important; } +.dark .bg-indigo-50 { background-color: rgb(49 46 129 / 0.15) !important; } +.dark .bg-orange-50 { background-color: rgb(124 45 18 / 0.15) !important; } +.dark .bg-brand-50 { background-color: rgb(var(--accent-900) / 0.15) !important; } +.dark .bg-emerald-50 { background-color: rgb(6 78 59 / 0.15) !important; } + +/* Colored border overrides */ +.dark .border-red-200 { border-color: rgb(127 29 29 / 0.4) !important; } +.dark .border-green-200 { border-color: rgb(6 78 59 / 0.4) !important; } +.dark .border-blue-200 { border-color: rgb(30 58 138 / 0.4) !important; } +.dark .border-yellow-200 { border-color: rgb(120 53 15 / 0.4) !important; } +.dark .border-amber-200 { border-color: rgb(120 53 15 / 0.4) !important; } +.dark .border-purple-200 { border-color: rgb(76 29 149 / 0.4) !important; } +.dark .border-indigo-200 { border-color: rgb(49 46 129 / 0.4) !important; } +.dark .border-brand-200 { border-color: rgb(var(--accent-700) / 0.4) !important; } + +/* Hover -50/-100 colored states */ +.dark .hover\:bg-red-50:hover { background-color: rgb(127 29 29 / 0.20) !important; } +.dark .hover\:bg-red-100:hover { background-color: rgb(127 29 29 / 0.30) !important; } +.dark .hover\:bg-green-50:hover { background-color: rgb(6 78 59 / 0.20) !important; } +.dark .hover\:bg-blue-50:hover { background-color: rgb(30 58 138 / 0.20) !important; } +.dark .hover\:bg-amber-50:hover { background-color: rgb(120 53 15 / 0.20) !important; } + +/* Missing text color overrides */ +.dark .text-amber-700 { color: rgb(251 191 36) !important; } +.dark .text-orange-600 { color: rgb(251 146 60) !important; } +.dark .text-green-600 { color: rgb(52 211 153) !important; } +.dark .text-emerald-700 { color: rgb(52 211 153) !important; } +.dark .text-brand-700 { color: rgb(var(--accent-400)) !important; } + +/* Divide override for gray-50 (ChargeabilityWidget sticky headers) */ +.dark .divide-gray-50 > * + * { border-color: rgb(var(--border-subtle)) !important; } + /* Slate-* normalization — map to CSS variable surfaces */ .dark .bg-slate-700, .dark .bg-slate-800 { diff --git a/apps/web/src/components/dashboard/WidgetContainer.tsx b/apps/web/src/components/dashboard/WidgetContainer.tsx index cae6422..f40fc62 100644 --- a/apps/web/src/components/dashboard/WidgetContainer.tsx +++ b/apps/web/src/components/dashboard/WidgetContainer.tsx @@ -30,7 +30,7 @@ export function WidgetContainer({ className={`flex flex-col h-full rounded-xl border overflow-hidden transition-all duration-200 ${ isDragging ? "shadow-xl border-brand-400 dark:border-brand-500 scale-[1.01] ring-2 ring-brand-400/30" - : "border-gray-200/80 bg-[linear-gradient(180deg,rgba(248,250,252,0.95),rgba(255,255,255,0.98))] shadow-sm hover:shadow-md hover:border-gray-300 dark:border-gray-700/60 dark:bg-[linear-gradient(180deg,rgba(17,24,39,0.96),rgba(17,24,39,0.92))] dark:hover:border-gray-600" + : "border-gray-200/80 bg-[linear-gradient(180deg,rgba(250,250,251,0.95),rgba(255,255,255,0.98))] shadow-sm hover:shadow-md hover:border-gray-300 dark:border-gray-700/60 dark:bg-[linear-gradient(180deg,rgba(22,23,26,0.97),rgba(16,17,19,0.95))] dark:hover:border-gray-600" }`} >
diff --git a/apps/web/src/components/dashboard/widgets/ChargeabilityWidget.tsx b/apps/web/src/components/dashboard/widgets/ChargeabilityWidget.tsx index 7bc73ed..95ea9af 100644 --- a/apps/web/src/components/dashboard/widgets/ChargeabilityWidget.tsx +++ b/apps/web/src/components/dashboard/widgets/ChargeabilityWidget.tsx @@ -413,7 +413,7 @@ export function ChargeabilityWidget({ config: _config, onConfigChange }: WidgetP handleSectionScroll(event, topVisibleCount, top.length, setTopVisibleCount) } > -

+

Top Chargeability @@ -425,7 +425,7 @@ export function ChargeabilityWidget({ config: _config, onConfigChange }: WidgetP ) : ( - + - + {visibleTop.map((r, i) => ( @@ -519,7 +519,7 @@ export function ChargeabilityWidget({ config: _config, onConfigChange }: WidgetP handleSectionScroll(event, watchVisibleCount, watchlist.length, setWatchVisibleCount) } > -

+

Watchlist (below target) @@ -531,7 +531,7 @@ export function ChargeabilityWidget({ config: _config, onConfigChange }: WidgetP ) : (

#
{i + 1}
- + - + {visibleWatchlist.map((r) => (
diff --git a/apps/web/src/components/dashboard/widgets/DemandWidget.tsx b/apps/web/src/components/dashboard/widgets/DemandWidget.tsx index b4cb9fc..a8a6783 100644 --- a/apps/web/src/components/dashboard/widgets/DemandWidget.tsx +++ b/apps/web/src/components/dashboard/widgets/DemandWidget.tsx @@ -147,7 +147,7 @@ export function DemandWidget({ config, onConfigChange }: WidgetProps) { {/* Table */}
- + - + {sorted.map((row) => ( - +
@@ -198,9 +198,9 @@ export function DemandWidget({ config, onConfigChange }: WidgetProps) {
{groupBy === "project" ? ( diff --git a/apps/web/src/components/dashboard/widgets/TopValueWidget.tsx b/apps/web/src/components/dashboard/widgets/TopValueWidget.tsx index 0c35280..1fa0e9f 100644 --- a/apps/web/src/components/dashboard/widgets/TopValueWidget.tsx +++ b/apps/web/src/components/dashboard/widgets/TopValueWidget.tsx @@ -167,7 +167,7 @@ export function TopValueWidget({ config, onConfigChange }: WidgetProps) { {})} />
- + - + {sorted.map((r, i) => ( - +
@@ -226,9 +226,9 @@ export function TopValueWidget({ config, onConfigChange }: WidgetProps) {
{i + 1} {r.eid} diff --git a/apps/web/src/components/dynamic-fields/DynamicFieldEditor.tsx b/apps/web/src/components/dynamic-fields/DynamicFieldEditor.tsx index 8db7bec..6010ccf 100644 --- a/apps/web/src/components/dynamic-fields/DynamicFieldEditor.tsx +++ b/apps/web/src/components/dynamic-fields/DynamicFieldEditor.tsx @@ -16,8 +16,8 @@ interface Props { const INPUT_BASE = "w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 transition-colors"; -const INPUT_NORMAL = "border-gray-300 bg-white text-gray-900"; -const INPUT_ERROR = "border-red-400 bg-red-50 text-gray-900"; +const INPUT_NORMAL = "border-gray-300 bg-white text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"; +const INPUT_ERROR = "border-red-400 bg-red-50 text-gray-900 dark:border-red-500 dark:text-gray-100"; function inputClass(hasError: boolean) { return clsx(INPUT_BASE, hasError ? INPUT_ERROR : INPUT_NORMAL); diff --git a/apps/web/src/components/ui/Button.tsx b/apps/web/src/components/ui/Button.tsx index 8ad7305..2e20015 100644 --- a/apps/web/src/components/ui/Button.tsx +++ b/apps/web/src/components/ui/Button.tsx @@ -8,8 +8,8 @@ interface ButtonProps extends ButtonHTMLAttributes { const variantClasses = { primary: "bg-brand-600 hover:bg-brand-700 text-white", - secondary: "bg-white hover:bg-gray-50 text-gray-700 border border-gray-300", - ghost: "text-gray-600 hover:bg-gray-100", + secondary: "bg-white hover:bg-gray-50 text-gray-700 border border-gray-300 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 dark:border-gray-600", + ghost: "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/[0.06]", danger: "bg-red-600 hover:bg-red-700 text-white", }; diff --git a/apps/web/src/lib/status-styles.ts b/apps/web/src/lib/status-styles.ts index 48ef64d..e757fe7 100644 --- a/apps/web/src/lib/status-styles.ts +++ b/apps/web/src/lib/status-styles.ts @@ -33,18 +33,18 @@ export const VACATION_TYPE_BADGE: Record = { }; export const PROJECT_STATUS_BADGE: Record = { - DRAFT: "bg-gray-100 text-gray-700", - ACTIVE: "bg-green-100 text-green-700", - ON_HOLD: "bg-yellow-100 text-yellow-700", - COMPLETED: "bg-blue-100 text-blue-700", - CANCELLED: "bg-red-100 text-red-700", + DRAFT: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300", + ACTIVE: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", + ON_HOLD: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400", + COMPLETED: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", + CANCELLED: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400", }; export const ORDER_TYPE_BADGE: Record = { - BD: "bg-purple-100 text-purple-700", - CHARGEABLE: "bg-green-100 text-green-700", - INTERNAL: "bg-blue-100 text-blue-700", - OVERHEAD: "bg-gray-100 text-gray-700", + BD: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400", + CHARGEABLE: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", + INTERNAL: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", + OVERHEAD: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300", }; /** Vacation overlay colors for timeline bars */