Rows per page standarized
This commit is contained in:
@@ -19,7 +19,7 @@ export default function AuditoriaPage() {
|
|||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const limit = 50;
|
const [limit, setLimit] = useState(10);
|
||||||
|
|
||||||
const actions: AuditAction[] = [
|
const actions: AuditAction[] = [
|
||||||
"CREATE",
|
"CREATE",
|
||||||
@@ -36,7 +36,8 @@ export default function AuditoriaPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAuditLogs();
|
fetchAuditLogs();
|
||||||
}, [currentPage, selectedAction, selectedTable]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage, selectedAction, selectedTable, limit]);
|
||||||
|
|
||||||
const fetchAuditLogs = async () => {
|
const fetchAuditLogs = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -78,6 +79,11 @@ export default function AuditoriaPage() {
|
|||||||
setShowDetails(true);
|
setShowDetails(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLimitChange = (newLimit: number) => {
|
||||||
|
setLimit(newLimit);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
const getActionColor = (action: AuditAction) => {
|
const getActionColor = (action: AuditAction) => {
|
||||||
const colors: Record<AuditAction, string> = {
|
const colors: Record<AuditAction, string> = {
|
||||||
CREATE: "bg-green-100 text-green-800",
|
CREATE: "bg-green-100 text-green-800",
|
||||||
@@ -296,25 +302,61 @@ export default function AuditoriaPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
{totalPages > 1 && (
|
{!loading && logs.length > 0 && (
|
||||||
<div className="mt-4 flex justify-center gap-2">
|
<div className="mt-4 flex flex-wrap items-center justify-between gap-4 px-4">
|
||||||
<button
|
{/* Page Info */}
|
||||||
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
<div className="text-sm text-gray-600">
|
||||||
disabled={currentPage === 1}
|
Mostrando{" "}
|
||||||
className="px-4 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
<span className="font-semibold text-gray-800">
|
||||||
>
|
{(currentPage - 1) * limit + 1}
|
||||||
Anterior
|
</span>{" "}
|
||||||
</button>
|
a{" "}
|
||||||
<span className="px-4 py-2">
|
<span className="font-semibold text-gray-800">
|
||||||
Página {currentPage} de {totalPages}
|
{Math.min(currentPage * limit, total)}
|
||||||
</span>
|
</span>{" "}
|
||||||
<button
|
de{" "}
|
||||||
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
|
<span className="font-semibold text-gray-800">{total}</span>{" "}
|
||||||
disabled={currentPage === totalPages}
|
registros
|
||||||
className="px-4 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
</div>
|
||||||
>
|
|
||||||
Siguiente
|
<div className="flex items-center gap-4">
|
||||||
</button>
|
{/* 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="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 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</button>
|
||||||
|
<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 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
Siguiente
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export default function UsersPage() {
|
|||||||
try {
|
try {
|
||||||
setLoadingUsers(true);
|
setLoadingUsers(true);
|
||||||
const usersResponse = await getAllUsers();
|
const usersResponse = await getAllUsers();
|
||||||
console.log('Users API response:', usersResponse);
|
|
||||||
|
|
||||||
const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({
|
const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({
|
||||||
id: apiUser.id,
|
id: apiUser.id,
|
||||||
@@ -312,7 +311,15 @@ export default function UsersPage() {
|
|||||||
]}
|
]}
|
||||||
data={filtered}
|
data={filtered}
|
||||||
onRowClick={(_, rowData) => setActiveUser(rowData as User)}
|
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>
|
</div>
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ export default function ConcentratorsTable({
|
|||||||
actionsColumnIndex: -1,
|
actionsColumnIndex: -1,
|
||||||
search: false,
|
search: false,
|
||||||
paging: true,
|
paging: true,
|
||||||
|
pageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50],
|
||||||
sorting: true,
|
sorting: true,
|
||||||
rowStyle: (rowData) => ({
|
rowStyle: (rowData) => ({
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function ConsumptionPage() {
|
|||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
const [pagination, setPagination] = useState<Pagination>({
|
const [pagination, setPagination] = useState<Pagination>({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 100,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
});
|
});
|
||||||
@@ -73,10 +73,12 @@ export default function ConsumptionPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadData = async (page = 1) => {
|
const loadData = async (page = 1, pageSize?: number) => {
|
||||||
setLoadingReadings(true);
|
setLoadingReadings(true);
|
||||||
setLoadingSummary(true);
|
setLoadingSummary(true);
|
||||||
|
|
||||||
|
const currentPageSize = pageSize || pagination.pageSize;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [readingsResult, summaryResult] = await Promise.all([
|
const [readingsResult, summaryResult] = await Promise.all([
|
||||||
fetchReadings({
|
fetchReadings({
|
||||||
@@ -84,7 +86,7 @@ export default function ConsumptionPage() {
|
|||||||
startDate: startDate || undefined,
|
startDate: startDate || undefined,
|
||||||
endDate: endDate || undefined,
|
endDate: endDate || undefined,
|
||||||
page,
|
page,
|
||||||
pageSize: 100,
|
pageSize: currentPageSize,
|
||||||
}),
|
}),
|
||||||
fetchConsumptionSummary(selectedProject || undefined),
|
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 exportToCSV = () => {
|
||||||
const headers = ["Fecha", "Medidor", "Serial", "Ubicación", "Valor", "Tipo", "Batería", "Señal"];
|
const headers = ["Fecha", "Medidor", "Serial", "Ubicación", "Valor", "Tipo", "Batería", "Señal"];
|
||||||
const rows = filteredReadings.map((r) => [
|
const rows = filteredReadings.map((r) => [
|
||||||
@@ -502,6 +513,88 @@ export default function ConsumptionPage() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -263,6 +263,8 @@ export default function ProjectsPage() {
|
|||||||
options={{
|
options={{
|
||||||
search: false,
|
search: false,
|
||||||
paging: true,
|
paging: true,
|
||||||
|
pageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50],
|
||||||
sorting: true,
|
sorting: true,
|
||||||
rowStyle: (rowData) => ({
|
rowStyle: (rowData) => ({
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
|||||||
Reference in New Issue
Block a user