From 380b96e1594a7296bbfa8bd0462e2b7c25129b1b Mon Sep 17 00:00:00 2001 From: Adrian Flores Cortes Date: Wed, 4 Feb 2026 01:14:11 -0600 Subject: [PATCH] feat(theme): Implement Dark Mode and Toast notifications - Add dark:* classes to 10 common components - Create ThemeProvider with Zustand persistence - Create ThemeToggle component (simple and full modes) - Implement Toast notification system (toastStore, useToast, ToastContainer) - Support success, error, warning, info toast types - Integrate ToastContainer in App.tsx Components updated: - Modal, EmptyState, StatusBadge, SearchInput - ConfirmDialog, PageHeader, FormField, ActionButtons - DataTable, LoadingSpinner Closes: G-005, G-008 Co-Authored-By: Claude Opus 4.5 --- web/src/App.tsx | 44 ++++- web/src/components/common/ActionButtons.tsx | 18 +- web/src/components/common/ConfirmDialog.tsx | 24 +-- web/src/components/common/DataTable.tsx | 34 ++-- web/src/components/common/EmptyState.tsx | 8 +- web/src/components/common/FormField.tsx | 52 +++--- web/src/components/common/LoadingSpinner.tsx | 4 +- web/src/components/common/Modal.tsx | 14 +- web/src/components/common/PageHeader.tsx | 8 +- web/src/components/common/SearchInput.tsx | 6 +- web/src/components/common/StatusBadge.tsx | 18 +- web/src/components/common/Toast.tsx | 116 +++++++++++++ web/src/components/common/index.ts | 3 + web/src/components/theme/ThemeProvider.tsx | 69 ++++++++ web/src/components/theme/ThemeToggle.tsx | 170 +++++++++++++++++++ web/src/components/theme/index.ts | 8 + web/src/hooks/index.ts | 2 + web/src/hooks/useToast.ts | 68 ++++++++ web/src/layouts/AdminLayout.tsx | 63 ++++--- web/src/stores/themeStore.ts | 100 +++++++++++ web/src/stores/toastStore.ts | 46 +++++ 21 files changed, 756 insertions(+), 119 deletions(-) create mode 100644 web/src/components/common/Toast.tsx create mode 100644 web/src/components/theme/ThemeProvider.tsx create mode 100644 web/src/components/theme/ThemeToggle.tsx create mode 100644 web/src/components/theme/index.ts create mode 100644 web/src/hooks/useToast.ts create mode 100644 web/src/stores/themeStore.ts create mode 100644 web/src/stores/toastStore.ts diff --git a/web/src/App.tsx b/web/src/App.tsx index d30286c..b6ae377 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -5,6 +5,9 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { AdminLayout } from './layouts/AdminLayout'; +import { ProtectedRoute } from './components/auth/ProtectedRoute'; +import { ThemeProvider } from './components/theme'; +import { ToastContainer } from './components/common'; import { FraccionamientosPage, FraccionamientoDetailPage, @@ -18,18 +21,32 @@ import { ConceptosPage, PresupuestosPage, PresupuestoDetailPage, EstimacionesPag import { OpportunitiesPage, TendersPage, ProposalsPage, VendorsPage } from './pages/admin/bidding'; import { IncidentesPage, CapacitacionesPage, InspeccionesPage, InspeccionDetailPage } from './pages/admin/hse'; import { AvancesObraPage, BitacoraObraPage, ProgramaObraPage, ControlAvancePage } from './pages/admin/obras'; +import { + CuentasContablesPage, + CuentasPorCobrarPage, + CuentasPorPagarPage, + PolizasPage, + FlujoEfectivoPage, + FacturasPage, +} from './pages/admin/finanzas'; import { LoginPage } from './pages/auth'; function App() { return ( - -
- + + +
+ {/* Ruta principal - redirect to admin dashboard */} } /> - {/* Portal Admin */} - }> + {/* Portal Admin - Protected */} + }> } /> {/* Dashboard */} @@ -81,6 +98,17 @@ function App() { } /> } /> + + {/* Finanzas */} + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/* Portal Supervisor */} @@ -95,8 +123,10 @@ function App() { {/* 404 */} } /> -
-
+ +
+
+ ); } diff --git a/web/src/components/common/ActionButtons.tsx b/web/src/components/common/ActionButtons.tsx index 3d50dde..9cbc86e 100644 --- a/web/src/components/common/ActionButtons.tsx +++ b/web/src/components/common/ActionButtons.tsx @@ -15,10 +15,10 @@ interface ActionButtonProps { } const variantClasses = { - default: 'text-gray-500 hover:text-blue-600 hover:bg-blue-50', - danger: 'text-gray-500 hover:text-red-600 hover:bg-red-50', - success: 'text-gray-500 hover:text-green-600 hover:bg-green-50', - warning: 'text-gray-500 hover:text-yellow-600 hover:bg-yellow-50', + default: 'text-foreground-muted hover:text-primary hover:bg-primary/10 dark:hover:bg-primary/20', + danger: 'text-foreground-muted hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20', + success: 'text-foreground-muted hover:text-green-600 dark:hover:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/20', + warning: 'text-foreground-muted hover:text-yellow-600 dark:hover:text-yellow-400 hover:bg-yellow-50 dark:hover:bg-yellow-900/20', }; export function ActionButton({ @@ -127,14 +127,14 @@ export function ActionMenu({ items, className }: ActionMenuProps) {
{isOpen && ( -
+
{items.map((item, index) => { const Icon = item.icon; return ( @@ -142,10 +142,10 @@ export function ActionMenu({ items, className }: ActionMenuProps) { key={index} type="button" className={clsx( - 'w-full px-4 py-2 text-left text-sm flex items-center gap-2', + 'w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition-colors', item.variant === 'danger' - ? 'text-red-600 hover:bg-red-50' - : 'text-gray-700 hover:bg-gray-50', + ? 'text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20' + : 'text-foreground hover:bg-background-muted dark:hover:bg-background-emphasis', item.disabled && 'opacity-50 cursor-not-allowed' )} onClick={() => { diff --git a/web/src/components/common/ConfirmDialog.tsx b/web/src/components/common/ConfirmDialog.tsx index 5a9fed1..b2a9abf 100644 --- a/web/src/components/common/ConfirmDialog.tsx +++ b/web/src/components/common/ConfirmDialog.tsx @@ -21,21 +21,21 @@ interface ConfirmDialogProps { const variantConfig = { danger: { icon: AlertTriangle, - iconBg: 'bg-red-100', - iconColor: 'text-red-600', - buttonClass: 'bg-red-600 hover:bg-red-700', + iconBg: 'bg-red-100 dark:bg-red-900/30', + iconColor: 'text-red-600 dark:text-red-400', + buttonClass: 'bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-600', }, warning: { icon: AlertCircle, - iconBg: 'bg-yellow-100', - iconColor: 'text-yellow-600', - buttonClass: 'bg-yellow-600 hover:bg-yellow-700', + iconBg: 'bg-yellow-100 dark:bg-yellow-900/30', + iconColor: 'text-yellow-600 dark:text-yellow-400', + buttonClass: 'bg-yellow-600 hover:bg-yellow-700 dark:bg-yellow-700 dark:hover:bg-yellow-600', }, info: { icon: Info, - iconBg: 'bg-blue-100', - iconColor: 'text-blue-600', - buttonClass: 'bg-blue-600 hover:bg-blue-700', + iconBg: 'bg-blue-100 dark:bg-blue-900/30', + iconColor: 'text-blue-600 dark:text-blue-400', + buttonClass: 'bg-primary hover:bg-primary-hover', }, }; @@ -75,12 +75,12 @@ export function ConfirmDialog({ >
-

{title}

-

{message}

+

{title}

+

{message}