Add dark mode support for tables and data pages

- Add CSS overrides for MaterialTable in dark mode
- Update page containers with dark:bg-zinc-950
- Update sidebars with dark mode (MetersSidebar, ConcentratorsSidebar)
- Update tables in AuditoriaPage, UsersPage, RolesPage
- Update ConsumptionPage with dark gradient background
- Update search inputs, select elements, and modals
- Add dark borders for card separation

Affected pages:
- MeterPage, ConcentratorsPage, ProjectsPage
- UsersPage, RolesPage, AuditoriaPage
- ConsumptionPage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Exteban08
2026-02-03 11:58:10 +00:00
parent c741b697d9
commit 0142ba740f
10 changed files with 240 additions and 144 deletions

View File

@@ -21,3 +21,99 @@ body {
body:where(.dark *) { body:where(.dark *) {
@apply bg-zinc-950 text-zinc-100; @apply bg-zinc-950 text-zinc-100;
} }
/* MaterialTable Dark Mode Overrides */
.dark .MuiPaper-root {
background-color: #18181b !important; /* zinc-900 */
color: #fafafa !important; /* zinc-50 */
}
.dark .MuiTableCell-root {
color: #e4e4e7 !important; /* zinc-200 */
border-bottom-color: #3f3f46 !important; /* zinc-700 */
}
.dark .MuiTableCell-head {
background-color: #18181b !important; /* zinc-900 */
color: #fafafa !important; /* zinc-50 */
}
.dark .MuiTableRow-root:hover {
background-color: #27272a !important; /* zinc-800 */
}
.dark .MuiTableRow-root.Mui-selected,
.dark .MuiTableRow-root.Mui-selected:hover {
background-color: #3f3f46 !important; /* zinc-700 */
}
.dark .MuiToolbar-root {
background-color: #18181b !important; /* zinc-900 */
color: #fafafa !important; /* zinc-50 */
}
.dark .MuiTypography-root {
color: #fafafa !important; /* zinc-50 */
}
.dark .MuiTablePagination-root {
color: #a1a1aa !important; /* zinc-400 */
}
.dark .MuiTablePagination-selectIcon {
color: #a1a1aa !important; /* zinc-400 */
}
.dark .MuiIconButton-root {
color: #a1a1aa !important; /* zinc-400 */
}
.dark .MuiIconButton-root:hover {
background-color: #3f3f46 !important; /* zinc-700 */
}
.dark .MuiIconButton-root.Mui-disabled {
color: #52525b !important; /* zinc-600 */
}
.dark .MuiInputBase-root {
color: #e4e4e7 !important; /* zinc-200 */
}
.dark .MuiInput-underline:before {
border-bottom-color: #3f3f46 !important; /* zinc-700 */
}
.dark .MuiSelect-icon {
color: #a1a1aa !important; /* zinc-400 */
}
.dark .MuiTableSortLabel-root {
color: #fafafa !important; /* zinc-50 */
}
.dark .MuiTableSortLabel-root:hover {
color: #ffffff !important;
}
.dark .MuiTableSortLabel-root.Mui-active {
color: #60a5fa !important; /* blue-400 */
}
.dark .MuiTableSortLabel-icon {
color: #60a5fa !important; /* blue-400 */
}
/* Dark mode for table row active/selected state */
.dark .MuiTableBody-root .MuiTableRow-root[style*="background-color: rgb(238, 242, 255)"],
.dark .MuiTableBody-root .MuiTableRow-root[style*="#EEF2FF"] {
background-color: #3f3f46 !important; /* zinc-700 */
}
/* Fix for inline styles - override white backgrounds */
.dark [style*="background-color: rgb(255, 255, 255)"],
.dark [style*="background-color: #FFFFFF"],
.dark [style*="background-color: #fff"],
.dark [style*="backgroundColor: rgb(255, 255, 255)"] {
background-color: #18181b !important; /* zinc-900 */
}

View File

@@ -114,20 +114,20 @@ export default function AuditoriaPage() {
const uniqueTables = Array.from(new Set(logs.map((log) => log.table_name))); const uniqueTables = Array.from(new Set(logs.map((log) => log.table_name)));
return ( return (
<div className="flex h-full"> <div className="flex h-full dark:bg-zinc-950">
{/* Sidebar */} {/* Sidebar */}
<aside className="w-64 border-r border-gray-200 bg-white p-4"> <aside className="w-64 border-r border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 p-4">
<h2 className="text-lg font-semibold mb-4">Filtros</h2> <h2 className="text-lg font-semibold mb-4 dark:text-white">Filtros</h2>
{/* Action Filter */} {/* Action Filter */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-zinc-300 mb-2">
Acción Acción
</label> </label>
<select <select
value={selectedAction} value={selectedAction}
onChange={(e) => setSelectedAction(e.target.value as AuditAction | "")} onChange={(e) => setSelectedAction(e.target.value as AuditAction | "")}
className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm" className="w-full border border-gray-300 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 rounded-md px-3 py-2 text-sm"
> >
<option value="">Todas las acciones</option> <option value="">Todas las acciones</option>
{actions.map((action) => ( {actions.map((action) => (
@@ -140,13 +140,13 @@ export default function AuditoriaPage() {
{/* Table Filter */} {/* Table Filter */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-zinc-300 mb-2">
Tabla Tabla
</label> </label>
<select <select
value={selectedTable} value={selectedTable}
onChange={(e) => setSelectedTable(e.target.value)} onChange={(e) => setSelectedTable(e.target.value)}
className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm" className="w-full border border-gray-300 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 rounded-md px-3 py-2 text-sm"
> >
<option value="">Todas las tablas</option> <option value="">Todas las tablas</option>
{uniqueTables.map((table) => ( {uniqueTables.map((table) => (
@@ -160,17 +160,17 @@ export default function AuditoriaPage() {
{/* Clear Filters */} {/* Clear Filters */}
<button <button
onClick={handleClearFilters} onClick={handleClearFilters}
className="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-2 rounded-md text-sm" className="w-full bg-gray-100 dark:bg-zinc-800 hover:bg-gray-200 dark:hover:bg-zinc-700 text-gray-700 dark:text-zinc-200 px-3 py-2 rounded-md text-sm"
> >
Limpiar filtros Limpiar filtros
</button> </button>
{/* Statistics */} {/* Statistics */}
<div className="mt-6 pt-6 border-t border-gray-200"> <div className="mt-6 pt-6 border-t border-gray-200 dark:border-zinc-700">
<h3 className="text-sm font-semibold text-gray-700 mb-2"> <h3 className="text-sm font-semibold text-gray-700 dark:text-zinc-300 mb-2">
Estadísticas Estadísticas
</h3> </h3>
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600 dark:text-zinc-400">
<p>Total de registros: {total}</p> <p>Total de registros: {total}</p>
<p>Página actual: {currentPage} de {totalPages}</p> <p>Página actual: {currentPage} de {totalPages}</p>
</div> </div>
@@ -180,9 +180,9 @@ export default function AuditoriaPage() {
{/* Main Content */} {/* Main Content */}
<main className="flex-1 flex flex-col"> <main className="flex-1 flex flex-col">
{/* Header */} {/* Header */}
<div className="border-b border-gray-200 bg-white px-6 py-4"> <div className="border-b border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 px-6 py-4">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-bold text-gray-900"> <h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Auditoría del Sistema Auditoría del Sistema
</h1> </h1>
<div className="flex gap-2"> <div className="flex gap-2">
@@ -205,7 +205,7 @@ export default function AuditoriaPage() {
placeholder="Buscar por usuario, email, tabla o descripción..." placeholder="Buscar por usuario, email, tabla o descripción..."
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md" className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 rounded-md"
/> />
</div> </div>
</div> </div>
@@ -213,52 +213,52 @@ export default function AuditoriaPage() {
{/* Table */} {/* Table */}
<div className="flex-1 overflow-auto p-6"> <div className="flex-1 overflow-auto p-6">
{loading ? ( {loading ? (
<div className="text-center py-12 text-gray-500"> <div className="text-center py-12 text-gray-500 dark:text-zinc-400">
Cargando registros... Cargando registros...
</div> </div>
) : filteredLogs.length === 0 ? ( ) : filteredLogs.length === 0 ? (
<div className="text-center py-12 text-gray-500"> <div className="text-center py-12 text-gray-500 dark:text-zinc-400">
No se encontraron registros de auditoría No se encontraron registros de auditoría
</div> </div>
) : ( ) : (
<div className="bg-white rounded-lg shadow overflow-hidden"> <div className="bg-white dark:bg-zinc-900 rounded-lg shadow dark:border dark:border-zinc-800 overflow-hidden">
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-full divide-y divide-gray-200 dark:divide-zinc-700">
<thead className="bg-gray-50"> <thead className="bg-gray-50 dark:bg-zinc-800">
<tr> <tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase">
Fecha/Hora Fecha/Hora
</th> </th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase">
Usuario Usuario
</th> </th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase">
Acción Acción
</th> </th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase">
Tabla Tabla
</th> </th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase">
Descripción Descripción
</th> </th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase">
Estado Estado
</th> </th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase">
Acciones Acciones
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200 dark:divide-zinc-700">
{filteredLogs.map((log) => ( {filteredLogs.map((log) => (
<tr key={log.id} className="hover:bg-gray-50"> <tr key={log.id} className="hover:bg-gray-50 dark:hover:bg-zinc-800">
<td className="px-4 py-3 text-sm text-gray-900 whitespace-nowrap"> <td className="px-4 py-3 text-sm text-gray-900 dark:text-zinc-100 whitespace-nowrap">
{new Date(log.created_at).toLocaleString("es-MX")} {new Date(log.created_at).toLocaleString("es-MX")}
</td> </td>
<td className="px-4 py-3 text-sm"> <td className="px-4 py-3 text-sm">
<div className="font-medium text-gray-900"> <div className="font-medium text-gray-900 dark:text-zinc-100">
{log.user_name} {log.user_name}
</div> </div>
<div className="text-gray-500">{log.user_email}</div> <div className="text-gray-500 dark:text-zinc-400">{log.user_email}</div>
</td> </td>
<td className="px-4 py-3 text-sm"> <td className="px-4 py-3 text-sm">
<span <span
@@ -269,10 +269,10 @@ export default function AuditoriaPage() {
{log.action} {log.action}
</span> </span>
</td> </td>
<td className="px-4 py-3 text-sm text-gray-900"> <td className="px-4 py-3 text-sm text-gray-900 dark:text-zinc-100">
{log.table_name} {log.table_name}
</td> </td>
<td className="px-4 py-3 text-sm text-gray-600"> <td className="px-4 py-3 text-sm text-gray-600 dark:text-zinc-400">
{log.description || "-"} {log.description || "-"}
</td> </td>
<td className="px-4 py-3 text-sm"> <td className="px-4 py-3 text-sm">
@@ -305,7 +305,7 @@ export default function AuditoriaPage() {
{!loading && logs.length > 0 && ( {!loading && logs.length > 0 && (
<div className="mt-4 flex flex-wrap items-center justify-between gap-4 px-4"> <div className="mt-4 flex flex-wrap items-center justify-between gap-4 px-4">
{/* Page Info */} {/* Page Info */}
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600 dark:text-zinc-400">
Mostrando{" "} Mostrando{" "}
<span className="font-semibold text-gray-800"> <span className="font-semibold text-gray-800">
{(currentPage - 1) * limit + 1} {(currentPage - 1) * limit + 1}
@@ -322,11 +322,11 @@ export default function AuditoriaPage() {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{/* Page Size Selector */} {/* Page Size Selector */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-gray-600">Filas por página:</span> <span className="text-sm text-gray-600 dark:text-zinc-400">Filas por página:</span>
<select <select
value={limit} value={limit}
onChange={(e) => handleLimitChange(Number(e.target.value))} onChange={(e) => handleLimitChange(Number(e.target.value))}
className="px-3 py-1.5 text-sm bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" className="px-3 py-1.5 text-sm bg-white dark:bg-zinc-800 dark:text-zinc-100 border border-gray-300 dark:border-zinc-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
> >
<option value={10}>10</option> <option value={10}>10</option>
<option value={20}>20</option> <option value={20}>20</option>
@@ -340,7 +340,7 @@ export default function AuditoriaPage() {
<button <button
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1} disabled={currentPage === 1}
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="px-4 py-2 border border-gray-300 dark:border-zinc-700 rounded-md hover:bg-gray-50 dark:hover:bg-zinc-800 dark:text-zinc-300 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
Anterior Anterior
</button> </button>
@@ -350,7 +350,7 @@ export default function AuditoriaPage() {
<button <button
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))} onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages} disabled={currentPage === totalPages}
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="px-4 py-2 border border-gray-300 dark:border-zinc-700 rounded-md hover:bg-gray-50 dark:hover:bg-zinc-800 dark:text-zinc-300 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
Siguiente Siguiente
</button> </button>
@@ -375,7 +375,7 @@ export default function AuditoriaPage() {
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
ID ID
</label> </label>
<p className="text-sm text-gray-900 font-mono"> <p className="text-sm text-gray-900 dark:text-zinc-100 font-mono">
{selectedLog.id} {selectedLog.id}
</p> </p>
</div> </div>
@@ -383,7 +383,7 @@ export default function AuditoriaPage() {
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
Fecha/Hora Fecha/Hora
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900 dark:text-zinc-100">
{new Date(selectedLog.created_at).toLocaleString("es-MX")} {new Date(selectedLog.created_at).toLocaleString("es-MX")}
</p> </p>
</div> </div>
@@ -391,7 +391,7 @@ export default function AuditoriaPage() {
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
Usuario Usuario
</label> </label>
<p className="text-sm text-gray-900">{selectedLog.user_name}</p> <p className="text-sm text-gray-900 dark:text-zinc-100">{selectedLog.user_name}</p>
<p className="text-xs text-gray-500">{selectedLog.user_email}</p> <p className="text-xs text-gray-500">{selectedLog.user_email}</p>
</div> </div>
<div> <div>
@@ -410,13 +410,13 @@ export default function AuditoriaPage() {
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
Tabla Tabla
</label> </label>
<p className="text-sm text-gray-900">{selectedLog.table_name}</p> <p className="text-sm text-gray-900 dark:text-zinc-100">{selectedLog.table_name}</p>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
Record ID Record ID
</label> </label>
<p className="text-sm text-gray-900 font-mono"> <p className="text-sm text-gray-900 dark:text-zinc-100 font-mono">
{selectedLog.record_id || "-"} {selectedLog.record_id || "-"}
</p> </p>
</div> </div>
@@ -424,7 +424,7 @@ export default function AuditoriaPage() {
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
IP Address IP Address
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900 dark:text-zinc-100">
{selectedLog.ip_address || "-"} {selectedLog.ip_address || "-"}
</p> </p>
</div> </div>
@@ -446,16 +446,16 @@ export default function AuditoriaPage() {
{selectedLog.description && ( {selectedLog.description && (
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-zinc-300 mb-2">
Descripción Descripción
</label> </label>
<p className="text-sm text-gray-900">{selectedLog.description}</p> <p className="text-sm text-gray-900 dark:text-zinc-100">{selectedLog.description}</p>
</div> </div>
)} )}
{selectedLog.old_values && ( {selectedLog.old_values && (
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-zinc-300 mb-2">
Valores Anteriores Valores Anteriores
</label> </label>
<pre className="bg-gray-50 p-3 rounded text-xs overflow-auto"> <pre className="bg-gray-50 p-3 rounded text-xs overflow-auto">
@@ -466,7 +466,7 @@ export default function AuditoriaPage() {
{selectedLog.new_values && ( {selectedLog.new_values && (
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-zinc-300 mb-2">
Valores Nuevos Valores Nuevos
</label> </label>
<pre className="bg-gray-50 p-3 rounded text-xs overflow-auto"> <pre className="bg-gray-50 p-3 rounded text-xs overflow-auto">
@@ -486,7 +486,7 @@ export default function AuditoriaPage() {
</div> </div>
)} )}
</div> </div>
<div className="p-6 border-t border-gray-200 flex justify-end"> <div className="p-6 border-t border-gray-200 dark:border-zinc-700 flex justify-end">
<button <button
onClick={() => setShowDetails(false)} onClick={() => setShowDetails(false)}
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-md" className="px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-md"

View File

@@ -110,34 +110,34 @@ export default function RolesPage() {
); );
return ( return (
<div className="flex gap-6 p-6 w-full bg-gray-100"> <div className="flex gap-6 p-6 w-full bg-gray-100 dark:bg-zinc-950">
{/* LEFT INFO SIDEBAR */} {/* LEFT INFO SIDEBAR */}
<div className="w-72 bg-white rounded-xl shadow p-4"> <div className="w-72 bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 rounded-xl shadow p-4">
<h3 className="text-xs font-semibold text-gray-500 mb-3">Role Information</h3> <h3 className="text-xs font-semibold text-gray-500 dark:text-zinc-400 mb-3">Role Information</h3>
<p className="text-sm text-gray-700 mb-4"> <p className="text-sm text-gray-700 dark:text-zinc-300 mb-4">
Manage system roles and their permissions. Roles define what actions users can perform. Manage system roles and their permissions. Roles define what actions users can perform.
</p> </p>
{activeRole && ( {activeRole && (
<div className="mt-6 pt-4 border-t"> <div className="mt-6 pt-4 border-t">
<h4 className="text-xs font-semibold text-gray-500 mb-2">Selected Role</h4> <h4 className="text-xs font-semibold text-gray-500 dark:text-zinc-400 mb-2">Selected Role</h4>
<div className="space-y-2"> <div className="space-y-2">
<div> <div>
<p className="text-xs text-gray-500">Name</p> <p className="text-xs text-gray-500 dark:text-zinc-400">Name</p>
<p className="text-sm font-medium">{activeRole.name}</p> <p className="text-sm font-medium dark:text-zinc-200">{activeRole.name}</p>
</div> </div>
<div> <div>
<p className="text-xs text-gray-500">Description</p> <p className="text-xs text-gray-500 dark:text-zinc-400">Description</p>
<p className="text-sm">{activeRole.description || "No description"}</p> <p className="text-sm dark:text-zinc-300">{activeRole.description || "No description"}</p>
</div> </div>
<div> <div>
<p className="text-xs text-gray-500">Created</p> <p className="text-xs text-gray-500 dark:text-zinc-400">Created</p>
<p className="text-sm">{new Date(activeRole.created_at).toLocaleDateString()}</p> <p className="text-sm dark:text-zinc-300">{new Date(activeRole.created_at).toLocaleDateString()}</p>
</div> </div>
{activeRole.permissions && Object.keys(activeRole.permissions).length > 0 && ( {activeRole.permissions && Object.keys(activeRole.permissions).length > 0 && (
<div> <div>
<p className="text-xs text-gray-500">Permissions</p> <p className="text-xs text-gray-500 dark:text-zinc-400">Permissions</p>
<p className="text-sm">{Object.keys(activeRole.permissions).length} permission groups</p> <p className="text-sm dark:text-zinc-300">{Object.keys(activeRole.permissions).length} permission groups</p>
</div> </div>
)} )}
</div> </div>
@@ -206,7 +206,7 @@ export default function RolesPage() {
{/* SEARCH */} {/* SEARCH */}
<input <input
className="bg-white rounded-lg shadow px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 dark:text-zinc-100 rounded-lg shadow px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:placeholder-zinc-500"
placeholder="Search role by name or description..." placeholder="Search role by name or description..."
value={search} value={search}
onChange={e => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}

View File

@@ -246,17 +246,17 @@ export default function UsersPage() {
}); });
return ( return (
<div className="flex gap-6 p-6 w-full bg-gray-100"> <div className="flex gap-6 p-6 w-full bg-gray-100 dark:bg-zinc-950">
{/* LEFT INFO SIDEBAR */} {/* LEFT INFO SIDEBAR */}
<div className="w-72 bg-white rounded-xl shadow p-4"> <div className="w-72 bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 rounded-xl shadow p-4">
<h3 className="text-xs font-semibold text-gray-500 mb-3">Filter Options</h3> <h3 className="text-xs font-semibold text-gray-500 dark:text-zinc-400 mb-3">Filter Options</h3>
<p className="text-sm text-gray-700 mb-4">Filter users by role</p> <p className="text-sm text-gray-700 dark:text-zinc-300 mb-4">Filter users by role</p>
<label className="text-xs font-semibold text-gray-500 mb-2 block">Role</label> <label className="text-xs font-semibold text-gray-500 mb-2 block">Role</label>
<select <select
value={selectedRoleFilter} value={selectedRoleFilter}
onChange={e => setSelectedRoleFilter(e.target.value)} onChange={e => setSelectedRoleFilter(e.target.value)}
className="w-full border px-3 py-2 rounded" className="w-full border dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 rounded"
disabled={loadingUsers} disabled={loadingUsers}
> >
<option value="">All Roles</option> <option value="">All Roles</option>
@@ -294,12 +294,12 @@ export default function UsersPage() {
</div> </div>
{/* SEARCH */} {/* SEARCH */}
<input className="bg-white rounded-lg shadow px-4 py-2 text-sm" placeholder="Search user..." value={search} onChange={e => setSearch(e.target.value)} /> <input className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-500 rounded-lg shadow px-4 py-2 text-sm" placeholder="Search user..." value={search} onChange={e => setSearch(e.target.value)} />
{/* TABLE */} {/* TABLE */}
{loadingUsers ? ( {loadingUsers ? (
<div className="bg-white rounded-xl shadow p-8 text-center"> <div className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 rounded-xl shadow p-8 text-center">
<p className="text-gray-500">Loading users...</p> <p className="text-gray-500 dark:text-zinc-400">Loading users...</p>
</div> </div>
) : ( ) : (
<MaterialTable <MaterialTable
@@ -329,8 +329,8 @@ export default function UsersPage() {
{/* MODAL */} {/* MODAL */}
{showModal && ( {showModal && (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center"> <div className="fixed inset-0 bg-black/40 flex items-center justify-center">
<div className="bg-white rounded-xl p-6 w-96 space-y-3"> <div className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 rounded-xl p-6 w-96 space-y-3">
<h2 className="text-lg font-semibold">{editingId ? "Edit User" : "Add User"}</h2> <h2 className="text-lg font-semibold dark:text-white">{editingId ? "Edit User" : "Add User"}</h2>
{error && ( {error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-2 rounded text-sm"> <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-2 rounded text-sm">
@@ -339,7 +339,7 @@ export default function UsersPage() {
)} )}
<input <input
className="w-full border px-3 py-2 rounded" className="w-full border dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 rounded"
placeholder="Full Name *" placeholder="Full Name *"
value={form.name} value={form.name}
onChange={e => setForm({...form, name: e.target.value})} onChange={e => setForm({...form, name: e.target.value})}
@@ -347,7 +347,7 @@ export default function UsersPage() {
/> />
<input <input
className="w-full border px-3 py-2 rounded" className="w-full border dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 rounded"
type="email" type="email"
placeholder="Email *" placeholder="Email *"
value={form.email} value={form.email}
@@ -357,7 +357,7 @@ export default function UsersPage() {
{!editingId && ( {!editingId && (
<input <input
className="w-full border px-3 py-2 rounded" className="w-full border dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 rounded"
type="password" type="password"
placeholder="Password * (min 8 characters)" placeholder="Password * (min 8 characters)"
value={form.password || ""} value={form.password || ""}
@@ -369,7 +369,7 @@ export default function UsersPage() {
<select <select
value={form.roleId} value={form.roleId}
onChange={e => setForm({...form, roleId: e.target.value, projectId: ""})} onChange={e => setForm({...form, roleId: e.target.value, projectId: ""})}
className="w-full border px-3 py-2 rounded" className="w-full border dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 rounded"
disabled={loadingModalRoles || saving} disabled={loadingModalRoles || saving}
> >
<option value="">{loadingModalRoles ? "Loading roles..." : "Select Role *"}</option> <option value="">{loadingModalRoles ? "Loading roles..." : "Select Role *"}</option>
@@ -380,7 +380,7 @@ export default function UsersPage() {
<select <select
value={form.projectId || ""} value={form.projectId || ""}
onChange={e => setForm({...form, projectId: e.target.value})} onChange={e => setForm({...form, projectId: e.target.value})}
className="w-full border px-3 py-2 rounded" className="w-full border dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 rounded"
disabled={loadingProjects || saving} disabled={loadingProjects || saving}
> >
<option value="">{loadingProjects ? "Loading projects..." : "Select Project *"}</option> <option value="">{loadingProjects ? "Loading projects..." : "Select Project *"}</option>

View File

@@ -136,7 +136,7 @@ export default function ConcentratorsPage() {
}; };
return ( return (
<div className="flex gap-6 p-6 w-full bg-gray-100"> <div className="flex gap-6 p-6 w-full bg-gray-100 dark:bg-zinc-950">
<aside className="w-[420px] shrink-0"> <aside className="w-[420px] shrink-0">
<ConcentratorsSidebar <ConcentratorsSidebar
loadingProjects={c.loadingProjects} loadingProjects={c.loadingProjects}
@@ -224,7 +224,7 @@ export default function ConcentratorsPage() {
</div> </div>
<input <input
className="bg-white rounded-lg shadow px-4 py-2 text-sm disabled:opacity-60 disabled:cursor-not-allowed" className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 dark:text-zinc-100 rounded-lg shadow px-4 py-2 text-sm disabled:opacity-60 disabled:cursor-not-allowed dark:placeholder-zinc-500"
placeholder="Buscar concentrador..." placeholder="Buscar concentrador..."
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}

View File

@@ -59,12 +59,12 @@ export default function ConcentratorsSidebar({
); );
return ( return (
<div className="bg-white rounded-xl shadow p-4 flex flex-col h-[calc(100vh-48px)]"> <div className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 rounded-xl shadow p-4 flex flex-col h-[calc(100vh-48px)]">
{/* Header */} {/* Header */}
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div> <div>
<p className="text-sm text-gray-500">Proyectos</p> <p className="text-sm text-gray-500 dark:text-zinc-400">Proyectos</p>
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400 dark:text-zinc-500">
Seleccionado:{" "} Seleccionado:{" "}
<span className="font-semibold"> <span className="font-semibold">
{projects.find((p) => p.id === selectedProject)?.name || "—"} {projects.find((p) => p.id === selectedProject)?.name || "—"}
@@ -86,7 +86,7 @@ export default function ConcentratorsSidebar({
<div className="mt-4 relative"> <div className="mt-4 relative">
{typesMenuOpen && ( {typesMenuOpen && (
<div className="absolute z-50 mt-2 w-full rounded-xl border border-gray-200 bg-white shadow-lg overflow-hidden"> <div className="absolute z-50 mt-2 w-full rounded-xl border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 shadow-lg overflow-hidden">
{options.map((opt) => { {options.map((opt) => {
const active = sampleView === opt.key; const active = sampleView === opt.key;
return ( return (
@@ -95,13 +95,13 @@ export default function ConcentratorsSidebar({
type="button" type="button"
onClick={() => onChangeSampleView(opt.key)} onClick={() => onChangeSampleView(opt.key)}
className={[ className={[
"w-full px-3 py-2 text-left text-sm flex items-center justify-between hover:bg-gray-50", "w-full px-3 py-2 text-left text-sm flex items-center justify-between hover:bg-gray-50 dark:hover:bg-zinc-700",
active ? "bg-blue-50/60" : "bg-white", active ? "bg-blue-50/60" : "bg-white",
].join(" ")} ].join(" ")}
> >
<span <span
className={`font-semibold ${ className={`font-semibold ${
active ? "text-blue-700" : "text-gray-700" active ? "text-blue-700" : "text-gray-700 dark:text-zinc-200"
}`} }`}
> >
{opt.label} {opt.label}
@@ -122,7 +122,7 @@ export default function ConcentratorsSidebar({
value={selectedMeterTypeId} value={selectedMeterTypeId}
onChange={(e) => onSelectMeterTypeId(e.target.value)} onChange={(e) => onSelectMeterTypeId(e.target.value)}
disabled={loadingMeterTypes} disabled={loadingMeterTypes}
className="w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-60 disabled:cursor-not-allowed" className="w-full rounded-lg border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-60 disabled:cursor-not-allowed"
> >
<option value="">Todos los tipos de toma</option> <option value="">Todos los tipos de toma</option>
{meterTypes.map((type) => ( {meterTypes.map((type) => (
@@ -136,9 +136,9 @@ export default function ConcentratorsSidebar({
{/* List */} {/* List */}
<div className="mt-4 overflow-y-auto flex-1 space-y-3 pr-1"> <div className="mt-4 overflow-y-auto flex-1 space-y-3 pr-1">
{loadingProjects ? ( {loadingProjects ? (
<div className="text-sm text-gray-500">Loading projects...</div> <div className="text-sm text-gray-500 dark:text-zinc-400">Loading projects...</div>
) : projects.length === 0 ? ( ) : projects.length === 0 ? (
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500 dark:text-zinc-400">
{selectedMeterTypeId {selectedMeterTypeId
? `No hay proyectos con el tipo de toma seleccionado.` ? `No hay proyectos con el tipo de toma seleccionado.`
: "No hay proyectos disponibles." : "No hay proyectos disponibles."
@@ -155,16 +155,16 @@ export default function ConcentratorsSidebar({
className={[ className={[
"rounded-xl border p-4 transition cursor-pointer", "rounded-xl border p-4 transition cursor-pointer",
active active
? "border-blue-600 bg-blue-50/40" ? "border-blue-600 bg-blue-50/40 dark:bg-blue-900/30"
: "border-gray-200 bg-white hover:bg-gray-50", : "border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 hover:bg-gray-50 dark:hover:bg-zinc-700",
].join(" ")} ].join(" ")}
> >
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div> <div>
<p className="text-sm font-semibold text-gray-800"> <p className="text-sm font-semibold text-gray-800 dark:text-zinc-100">
{p.name} {p.name}
</p> </p>
<p className="text-xs text-gray-500">{p.region}</p> <p className="text-xs text-gray-500 dark:text-zinc-400">{p.region}</p>
</div> </div>
<span <span
@@ -172,7 +172,7 @@ export default function ConcentratorsSidebar({
"text-xs font-semibold px-2 py-1 rounded-full", "text-xs font-semibold px-2 py-1 rounded-full",
p.status === "ACTIVO" p.status === "ACTIVO"
? "bg-green-100 text-green-700" ? "bg-green-100 text-green-700"
: "bg-gray-200 text-gray-700", : "bg-gray-200 text-gray-700 dark:text-zinc-200",
].join(" ")} ].join(" ")}
> >
{p.status} {p.status}
@@ -181,34 +181,34 @@ export default function ConcentratorsSidebar({
<div className="mt-3 grid grid-cols-2 gap-2 text-xs"> <div className="mt-3 grid grid-cols-2 gap-2 text-xs">
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Subproyectos</span> <span className="text-gray-500 dark:text-zinc-400">Subproyectos</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.projects} {p.projects}
</span> </span>
</div> </div>
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Concentradores</span> <span className="text-gray-500 dark:text-zinc-400">Concentradores</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.concentrators} {p.concentrators}
</span> </span>
</div> </div>
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Alertas activas</span> <span className="text-gray-500 dark:text-zinc-400">Alertas activas</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.activeAlerts} {p.activeAlerts}
</span> </span>
</div> </div>
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Última sync</span> <span className="text-gray-500 dark:text-zinc-400">Última sync</span>
<span className="font-medium text-gray-800">{p.lastSync}</span> <span className="font-medium text-gray-800 dark:text-zinc-100">{p.lastSync}</span>
</div> </div>
<div className="col-span-2 flex justify-between gap-2"> <div className="col-span-2 flex justify-between gap-2">
<span className="text-gray-500">Responsable</span> <span className="text-gray-500 dark:text-zinc-400">Responsable</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.contact} {p.contact}
</span> </span>
</div> </div>
@@ -237,7 +237,7 @@ export default function ConcentratorsSidebar({
)} )}
</div> </div>
<div className="pt-3 border-t text-xs text-gray-500"> <div className="pt-3 border-t text-xs text-gray-500 dark:text-zinc-400">
Nota: region/alertas/última sync están en modo demostración hasta integrar Nota: region/alertas/última sync están en modo demostración hasta integrar
backend. backend.
</div> </div>

View File

@@ -211,13 +211,13 @@ export default function ConsumptionPage() {
const activeFiltersCount = [selectedProject, startDate, endDate].filter(Boolean).length; const activeFiltersCount = [selectedProject, startDate, endDate].filter(Boolean).length;
return ( return (
<div className="min-h-full bg-gradient-to-br from-slate-50 via-blue-50/30 to-indigo-50/50 p-6"> <div className="min-h-full bg-gradient-to-br from-slate-50 via-blue-50/30 to-indigo-50/50 dark:from-zinc-950 dark:via-zinc-950 dark:to-zinc-950 p-6">
<div className="max-w-[1600px] mx-auto space-y-6"> <div className="max-w-[1600px] mx-auto space-y-6">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-slate-800">Consumo de Agua</h1> <h1 className="text-2xl font-bold text-slate-800 dark:text-white">Consumo de Agua</h1>
<p className="text-slate-500 text-sm mt-0.5"> <p className="text-slate-500 dark:text-zinc-400 text-sm mt-0.5">
Monitoreo en tiempo real de lecturas Monitoreo en tiempo real de lecturas
</p> </p>
</div> </div>
@@ -231,7 +231,7 @@ export default function ConsumptionPage() {
</button> </button>
<button <button
onClick={() => loadData(pagination.page)} onClick={() => loadData(pagination.page)}
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-slate-600 bg-white border border-slate-200 rounded-xl hover:bg-slate-50 hover:border-slate-300 transition-all shadow-sm" className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-slate-600 dark:text-zinc-300 bg-white dark:bg-zinc-800 border border-slate-200 dark:border-zinc-700 rounded-xl hover:bg-slate-50 dark:hover:bg-zinc-700 hover:border-slate-300 dark:hover:border-zinc-600 transition-all shadow-sm"
> >
<RefreshCcw size={16} /> <RefreshCcw size={16} />
Actualizar Actualizar
@@ -288,9 +288,9 @@ export default function ConsumptionPage() {
</div> </div>
{/* Table Card */} {/* Table Card */}
<div className="bg-white rounded-2xl shadow-sm shadow-slate-200/50 border border-slate-200/60 overflow-hidden"> <div className="bg-white dark:bg-zinc-900 rounded-2xl shadow-sm shadow-slate-200/50 dark:shadow-none border border-slate-200/60 dark:border-zinc-800 overflow-hidden">
{/* Table Header */} {/* Table Header */}
<div className="px-5 py-4 border-b border-slate-100 flex flex-wrap items-center justify-between gap-4"> <div className="px-5 py-4 border-b border-slate-100 dark:border-zinc-800 flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="relative"> <div className="relative">
<Search <Search
@@ -302,7 +302,7 @@ export default function ConsumptionPage() {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
placeholder="Buscar lecturas..." placeholder="Buscar lecturas..."
className="w-64 pl-10 pr-4 py-2 text-sm bg-slate-50 border-0 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:bg-white transition-all" className="w-64 pl-10 pr-4 py-2 text-sm bg-slate-50 dark:bg-zinc-800 dark:text-zinc-100 border-0 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:bg-white transition-all"
/> />
</div> </div>
@@ -334,9 +334,9 @@ export default function ConsumptionPage() {
)} )}
</div> </div>
<div className="flex items-center gap-4 text-sm text-slate-500"> <div className="flex items-center gap-4 text-sm text-slate-500 dark:text-zinc-400">
<span> <span>
<span className="font-semibold text-slate-700">{filteredReadings.length}</span>{" "} <span className="font-semibold text-slate-700 dark:text-zinc-200">{filteredReadings.length}</span>{" "}
{pagination.total > filteredReadings.length && `de ${pagination.total} `} {pagination.total > filteredReadings.length && `de ${pagination.total} `}
lecturas lecturas
</span> </span>
@@ -367,7 +367,7 @@ export default function ConsumptionPage() {
{/* Filters Panel */} {/* Filters Panel */}
{showFilters && ( {showFilters && (
<div className="px-5 py-4 bg-slate-50/50 border-b border-slate-100 flex flex-wrap items-center gap-4"> <div className="px-5 py-4 bg-slate-50/50 border-b border-slate-100 dark:border-zinc-800 flex flex-wrap items-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="text-xs font-medium text-slate-500 uppercase tracking-wide"> <label className="text-xs font-medium text-slate-500 uppercase tracking-wide">
Proyecto Proyecto
@@ -634,7 +634,7 @@ function StatCard({
{loading ? ( {loading ? (
<div className="h-8 w-24 bg-slate-100 rounded-lg animate-pulse" /> <div className="h-8 w-24 bg-slate-100 rounded-lg animate-pulse" />
) : ( ) : (
<p className="text-2xl font-bold text-slate-800">{value}</p> <p className="text-2xl font-bold text-slate-800 dark:text-white">{value}</p>
)} )}
{trend && !loading && ( {trend && !loading && (
<div className="inline-flex items-center gap-1 text-xs font-medium text-emerald-600 bg-emerald-50 px-2 py-0.5 rounded-full"> <div className="inline-flex items-center gap-1 text-xs font-medium text-emerald-600 bg-emerald-50 px-2 py-0.5 rounded-full">

View File

@@ -207,7 +207,7 @@ export default function MetersPage({
}; };
return ( return (
<div className="flex gap-6 p-6 w-full bg-gray-100"> <div className="flex gap-6 p-6 w-full bg-gray-100 dark:bg-zinc-950">
{/* SIDEBAR */} {/* SIDEBAR */}
<MetersSidebar <MetersSidebar
loadingProjects={m.loadingProjects} loadingProjects={m.loadingProjects}
@@ -289,7 +289,7 @@ export default function MetersPage({
</div> </div>
<input <input
className="bg-white rounded-lg shadow px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 dark:text-zinc-100 rounded-lg shadow px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:placeholder-zinc-500"
placeholder="Buscar por nombre, serial, ubicación o concentrador..." placeholder="Buscar por nombre, serial, ubicación o concentrador..."
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}

View File

@@ -78,12 +78,12 @@ export default function MetersSidebar({
return ( return (
<aside className="w-[420px] shrink-0"> <aside className="w-[420px] shrink-0">
<div className="bg-white rounded-xl shadow p-4 flex flex-col h-[calc(100vh-48px)]"> <div className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 rounded-xl shadow p-4 flex flex-col h-[calc(100vh-48px)]">
{/* Header */} {/* Header */}
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div> <div>
<p className="text-sm text-gray-500">Proyectos</p> <p className="text-sm text-gray-500 dark:text-zinc-400">Proyectos</p>
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400 dark:text-zinc-500">
Seleccionado:{" "} Seleccionado:{" "}
<span className="font-semibold"> <span className="font-semibold">
{selectedProject || "—"} {selectedProject || "—"}
@@ -106,7 +106,7 @@ export default function MetersSidebar({
<div className="mt-4 relative" ref={menuRef}> <div className="mt-4 relative" ref={menuRef}>
{typesMenuOpen && ( {typesMenuOpen && (
<div className="absolute z-50 mt-2 w-full rounded-xl border border-gray-200 bg-white shadow-lg overflow-hidden"> <div className="absolute z-50 mt-2 w-full rounded-xl border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 shadow-lg overflow-hidden">
{TAKE_TYPE_OPTIONS.map((opt) => { {TAKE_TYPE_OPTIONS.map((opt) => {
const active = takeType === opt.key; const active = takeType === opt.key;
@@ -125,13 +125,13 @@ export default function MetersSidebar({
} }
}} }}
className={[ className={[
"w-full px-3 py-2 text-left text-sm flex items-center justify-between hover:bg-gray-50", "w-full px-3 py-2 text-left text-sm flex items-center justify-between hover:bg-gray-50 dark:hover:bg-zinc-700",
active ? "bg-blue-50/60" : "bg-white", active ? "bg-blue-50/60" : "bg-white",
].join(" ")} ].join(" ")}
> >
<span <span
className={`font-semibold ${ className={`font-semibold ${
active ? "text-blue-700" : "text-gray-700" active ? "text-blue-700" : "text-gray-700 dark:text-zinc-200"
}`} }`}
> >
{opt.label} {opt.label}
@@ -153,7 +153,7 @@ export default function MetersSidebar({
value={selectedMeterTypeId} value={selectedMeterTypeId}
onChange={(e) => onSelectMeterTypeId(e.target.value)} onChange={(e) => onSelectMeterTypeId(e.target.value)}
disabled={loadingMeterTypes} disabled={loadingMeterTypes}
className="w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-60 disabled:cursor-not-allowed" className="w-full rounded-lg border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 dark:text-zinc-100 px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-60 disabled:cursor-not-allowed"
> >
<option value="">Todos los tipos de toma</option> <option value="">Todos los tipos de toma</option>
{meterTypes.map((type) => ( {meterTypes.map((type) => (
@@ -167,7 +167,7 @@ export default function MetersSidebar({
{/* List */} {/* List */}
<div className="mt-4 overflow-y-auto flex-1 space-y-3 pr-1"> <div className="mt-4 overflow-y-auto flex-1 space-y-3 pr-1">
{loadingProjects ? ( {loadingProjects ? (
<div className="text-sm text-gray-500">Cargando proyectos...</div> <div className="text-sm text-gray-500 dark:text-zinc-400">Cargando proyectos...</div>
) : projects.length === 0 ? ( ) : projects.length === 0 ? (
<div className="text-sm text-gray-500 text-center py-10"> <div className="text-sm text-gray-500 text-center py-10">
{selectedMeterTypeId {selectedMeterTypeId
@@ -190,8 +190,8 @@ export default function MetersSidebar({
className={[ className={[
"rounded-xl border p-4 transition cursor-pointer", "rounded-xl border p-4 transition cursor-pointer",
active active
? "border-blue-600 bg-blue-50/40" ? "border-blue-600 bg-blue-50/40 dark:bg-blue-900/30"
: "border-gray-200 bg-white hover:bg-gray-50", : "border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 hover:bg-gray-50 dark:hover:bg-zinc-700",
isMockMode ? "opacity-90" : "", isMockMode ? "opacity-90" : "",
].join(" ")} ].join(" ")}
title={ title={
@@ -202,10 +202,10 @@ export default function MetersSidebar({
> >
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div> <div>
<p className="text-sm font-semibold text-gray-800"> <p className="text-sm font-semibold text-gray-800 dark:text-zinc-100">
{p.name} {p.name}
</p> </p>
<p className="text-xs text-gray-500">{p.region}</p> <p className="text-xs text-gray-500 dark:text-zinc-400">{p.region}</p>
</div> </div>
<span <span
@@ -213,7 +213,7 @@ export default function MetersSidebar({
"text-xs font-semibold px-2 py-1 rounded-full", "text-xs font-semibold px-2 py-1 rounded-full",
p.status === "ACTIVO" p.status === "ACTIVO"
? "bg-green-100 text-green-700" ? "bg-green-100 text-green-700"
: "bg-gray-200 text-gray-700", : "bg-gray-200 text-gray-700 dark:text-zinc-200",
].join(" ")} ].join(" ")}
> >
{p.status} {p.status}
@@ -222,36 +222,36 @@ export default function MetersSidebar({
<div className="mt-3 grid grid-cols-2 gap-2 text-xs"> <div className="mt-3 grid grid-cols-2 gap-2 text-xs">
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Subproyectos</span> <span className="text-gray-500 dark:text-zinc-400">Subproyectos</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.projects} {p.projects}
</span> </span>
</div> </div>
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Medidores</span> <span className="text-gray-500 dark:text-zinc-400">Medidores</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.meters} {p.meters}
</span> </span>
</div> </div>
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Alertas activas</span> <span className="text-gray-500 dark:text-zinc-400">Alertas activas</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.activeAlerts} {p.activeAlerts}
</span> </span>
</div> </div>
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<span className="text-gray-500">Última sync</span> <span className="text-gray-500 dark:text-zinc-400">Última sync</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.lastSync} {p.lastSync}
</span> </span>
</div> </div>
<div className="col-span-2 flex justify-between gap-2"> <div className="col-span-2 flex justify-between gap-2">
<span className="text-gray-500">Responsable</span> <span className="text-gray-500 dark:text-zinc-400">Responsable</span>
<span className="font-medium text-gray-800"> <span className="font-medium text-gray-800 dark:text-zinc-100">
{p.contact} {p.contact}
</span> </span>
</div> </div>
@@ -288,7 +288,7 @@ export default function MetersSidebar({
)} )}
</div> </div>
<div className="pt-3 border-t text-xs text-gray-500"> <div className="pt-3 border-t text-xs text-gray-500 dark:text-zinc-400">
Nota: region/alertas/última sync están en modo demostración hasta Nota: region/alertas/última sync están en modo demostración hasta
integrar backend. integrar backend.
</div> </div>

View File

@@ -159,7 +159,7 @@ export default function ProjectsPage() {
); );
return ( return (
<div className="flex gap-6 p-6 w-full bg-gray-100"> <div className="flex gap-6 p-6 w-full bg-gray-100 dark:bg-zinc-950">
<div className="flex-1 flex flex-col gap-6"> <div className="flex-1 flex flex-col gap-6">
{/* HEADER */} {/* HEADER */}
<div <div
@@ -214,7 +214,7 @@ export default function ProjectsPage() {
{/* SEARCH */} {/* SEARCH */}
<input <input
className="bg-white rounded-lg shadow px-4 py-2 text-sm" className="bg-white dark:bg-zinc-900 dark:border dark:border-zinc-800 dark:text-zinc-100 rounded-lg shadow px-4 py-2 text-sm dark:placeholder-zinc-500"
placeholder="Buscar proyecto..." placeholder="Buscar proyecto..."
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}