Rows per page standarized

This commit is contained in:
2026-02-03 01:26:59 -06:00
parent d1770b550a
commit 31ab977f97
5 changed files with 172 additions and 26 deletions

View File

@@ -19,7 +19,7 @@ export default function AuditoriaPage() {
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [total, setTotal] = useState(0);
const limit = 50;
const [limit, setLimit] = useState(10);
const actions: AuditAction[] = [
"CREATE",
@@ -36,7 +36,8 @@ export default function AuditoriaPage() {
useEffect(() => {
fetchAuditLogs();
}, [currentPage, selectedAction, selectedTable]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage, selectedAction, selectedTable, limit]);
const fetchAuditLogs = async () => {
try {
@@ -78,6 +79,11 @@ export default function AuditoriaPage() {
setShowDetails(true);
};
const handleLimitChange = (newLimit: number) => {
setLimit(newLimit);
setCurrentPage(1);
};
const getActionColor = (action: AuditAction) => {
const colors: Record<AuditAction, string> = {
CREATE: "bg-green-100 text-green-800",
@@ -296,28 +302,64 @@ export default function AuditoriaPage() {
)}
{/* Pagination */}
{!loading && logs.length > 0 && (
<div className="mt-4 flex flex-wrap items-center justify-between gap-4 px-4">
{/* Page Info */}
<div className="text-sm text-gray-600">
Mostrando{" "}
<span className="font-semibold text-gray-800">
{(currentPage - 1) * limit + 1}
</span>{" "}
a{" "}
<span className="font-semibold text-gray-800">
{Math.min(currentPage * limit, total)}
</span>{" "}
de{" "}
<span className="font-semibold text-gray-800">{total}</span>{" "}
registros
</div>
<div className="flex items-center gap-4">
{/* Page Size Selector */}
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600">Filas por página:</span>
<select
value={limit}
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"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
</div>
{/* Page Navigation */}
{totalPages > 1 && (
<div className="mt-4 flex justify-center gap-2">
<div className="flex items-center gap-2">
<button
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
className="px-4 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Anterior
</button>
<span className="px-4 py-2">
<span className="px-4 py-2 text-sm text-gray-700">
Página {currentPage} de {totalPages}
</span>
<button
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
className="px-4 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Siguiente
</button>
</div>
)}
</div>
</div>
)}
</div>
</main>
{/* Details Modal */}

View File

@@ -58,7 +58,6 @@ export default function UsersPage() {
try {
setLoadingUsers(true);
const usersResponse = await getAllUsers();
console.log('Users API response:', usersResponse);
const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({
id: apiUser.id,
@@ -312,7 +311,15 @@ export default function UsersPage() {
]}
data={filtered}
onRowClick={(_, rowData) => setActiveUser(rowData as User)}
options={{ actionsColumnIndex: -1, search: false, paging: true, sorting: true, rowStyle: rowData => ({ backgroundColor: activeUser?.id === (rowData as User).id ? "#EEF2FF" : "#FFFFFF" }) }}
options={{
actionsColumnIndex: -1,
search: false,
paging: true,
pageSize: 10,
pageSizeOptions: [10, 20, 50],
sorting: true,
rowStyle: rowData => ({ backgroundColor: activeUser?.id === (rowData as User).id ? "#EEF2FF" : "#FFFFFF" })
}}
/>
)}
</div>

View File

@@ -94,6 +94,8 @@ export default function ConcentratorsTable({
actionsColumnIndex: -1,
search: false,
paging: true,
pageSize: 10,
pageSizeOptions: [10, 20, 50],
sorting: true,
rowStyle: (rowData) => ({
backgroundColor:

View File

@@ -35,7 +35,7 @@ export default function ConsumptionPage() {
const [projects, setProjects] = useState<Project[]>([]);
const [pagination, setPagination] = useState<Pagination>({
page: 1,
pageSize: 100,
pageSize: 10,
total: 0,
totalPages: 0,
});
@@ -73,10 +73,12 @@ export default function ConsumptionPage() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loadData = async (page = 1) => {
const loadData = async (page = 1, pageSize?: number) => {
setLoadingReadings(true);
setLoadingSummary(true);
const currentPageSize = pageSize || pagination.pageSize;
try {
const [readingsResult, summaryResult] = await Promise.all([
fetchReadings({
@@ -84,7 +86,7 @@ export default function ConsumptionPage() {
startDate: startDate || undefined,
endDate: endDate || undefined,
page,
pageSize: 100,
pageSize: currentPageSize,
}),
fetchConsumptionSummary(selectedProject || undefined),
]);
@@ -167,6 +169,15 @@ export default function ConsumptionPage() {
});
};
const handlePageChange = (newPage: number) => {
loadData(newPage);
};
const handlePageSizeChange = (newPageSize: number) => {
setPagination({ ...pagination, pageSize: newPageSize, page: 1 });
loadData(1, newPageSize);
};
const exportToCSV = () => {
const headers = ["Fecha", "Medidor", "Serial", "Ubicación", "Valor", "Tipo", "Batería", "Señal"];
const rows = filteredReadings.map((r) => [
@@ -502,6 +513,88 @@ export default function ConsumptionPage() {
</tbody>
</table>
</div>
{!loadingReadings && filteredReadings.length > 0 && (
<div className="px-5 py-4 border-t border-slate-100 flex flex-wrap items-center justify-between gap-4">
<div className="text-sm text-slate-600">
Mostrando{" "}
<span className="font-semibold text-slate-800">
{(pagination.page - 1) * pagination.pageSize + 1}
</span>{" "}
a{" "}
<span className="font-semibold text-slate-800">
{Math.min(pagination.page * pagination.pageSize, pagination.total)}
</span>{" "}
de{" "}
<span className="font-semibold text-slate-800">{pagination.total}</span>{" "}
resultados
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-sm text-slate-600">Filas por página:</span>
<select
value={pagination.pageSize}
onChange={(e) => handlePageSizeChange(Number(e.target.value))}
className="px-3 py-1.5 text-sm bg-white border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => handlePageChange(pagination.page - 1)}
disabled={pagination.page === 1}
className="p-2 rounded-lg hover:bg-slate-100 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
<ChevronLeft size={18} className="text-slate-600" />
</button>
<div className="flex items-center gap-1">
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1)
.filter((pageNum) => {
if (pageNum === 1 || pageNum === pagination.totalPages) return true;
if (Math.abs(pageNum - pagination.page) <= 1) return true;
return false;
})
.map((pageNum, idx, arr) => {
const prevNum = arr[idx - 1];
const showEllipsis = prevNum && pageNum - prevNum > 1;
return (
<div key={pageNum} className="flex items-center">
{showEllipsis && (
<span className="px-2 text-slate-400">...</span>
)}
<button
onClick={() => handlePageChange(pageNum)}
className={`min-w-[36px] px-3 py-1.5 text-sm rounded-lg transition-colors ${
pageNum === pagination.page
? "bg-blue-600 text-white font-semibold"
: "text-slate-600 hover:bg-slate-100"
}`}
>
{pageNum}
</button>
</div>
);
})}
</div>
<button
onClick={() => handlePageChange(pagination.page + 1)}
disabled={pagination.page === pagination.totalPages}
className="p-2 rounded-lg hover:bg-slate-100 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
<ChevronRight size={18} className="text-slate-600" />
</button>
</div>
</div>
</div>
)}
</div>
</div>

View File

@@ -263,6 +263,8 @@ export default function ProjectsPage() {
options={{
search: false,
paging: true,
pageSize: 10,
pageSizeOptions: [10, 20, 50],
sorting: true,
rowStyle: (rowData) => ({
backgroundColor: