Improve the frontend and repository layer

This commit is contained in:
Urtzi Alfaro
2025-10-23 07:44:54 +02:00
parent 8d30172483
commit 07c33fa578
112 changed files with 14726 additions and 2733 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState, useMemo, useCallback } from 'react';
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardHeader, CardBody } from '../../ui/Card';
import { Badge } from '../../ui/Badge';
import { SeverityBadge } from '../../ui/Badge';
import { Button } from '../../ui/Button';
import { useNotifications } from '../../../hooks/useNotifications';
import { useAlertFilters } from '../../../hooks/useAlertFilters';
@@ -18,6 +18,8 @@ import {
AlertTriangle,
AlertCircle,
Clock,
ChevronLeft,
ChevronRight,
} from 'lucide-react';
import AlertFilters from './AlertFilters';
import AlertGroupHeader from './AlertGroupHeader';
@@ -61,6 +63,10 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
const [showBulkActions, setShowBulkActions] = useState(false);
const [showAnalyticsPanel, setShowAnalyticsPanel] = useState(false);
// Pagination state
const ALERTS_PER_PAGE = 3;
const [currentPage, setCurrentPage] = useState(1);
const {
notifications,
isConnected,
@@ -121,6 +127,32 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
);
}, [groupedAlerts, isGroupCollapsed]);
// Reset pagination when filters change
useEffect(() => {
setCurrentPage(1);
}, [filters, groupingMode]);
// Pagination calculations
const totalAlerts = flatAlerts.length;
const totalPages = Math.ceil(totalAlerts / ALERTS_PER_PAGE);
const startIndex = (currentPage - 1) * ALERTS_PER_PAGE;
const endIndex = startIndex + ALERTS_PER_PAGE;
// Paginated alerts - slice the flat alerts for current page
const paginatedAlerts = useMemo(() => {
const alertsToShow = flatAlerts.slice(startIndex, endIndex);
const alertIds = new Set(alertsToShow.map(a => a.id));
// Filter groups to only show alerts on current page
return groupedAlerts
.map(group => ({
...group,
alerts: group.alerts.filter(alert => alertIds.has(alert.id)),
count: group.alerts.filter(alert => alertIds.has(alert.id)).length,
}))
.filter(group => group.alerts.length > 0);
}, [groupedAlerts, flatAlerts, startIndex, endIndex]);
const { focusedIndex } = useKeyboardNavigation(
flatAlerts.length,
{
@@ -296,22 +328,18 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
{/* Alert count badges */}
<div className="flex items-center gap-2">
{urgentCount > 0 && (
<Badge
variant="error"
<SeverityBadge
severity="high"
count={urgentCount}
size="sm"
icon={<AlertTriangle className="w-4 h-4" />}
>
{urgentCount} Alto
</Badge>
/>
)}
{highCount > 0 && (
<Badge
variant="warning"
<SeverityBadge
severity="medium"
count={highCount}
size="sm"
icon={<AlertCircle className="w-4 h-4" />}
>
{highCount} Medio
</Badge>
/>
)}
</div>
@@ -402,7 +430,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
</div>
) : (
<div className="space-y-3 p-4">
{groupedAlerts.map((group) => (
{paginatedAlerts.map((group) => (
<div key={group.id}>
{(group.count > 1 || groupingMode !== 'none') && (
<div className="mb-3">
@@ -448,24 +476,58 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
backgroundColor: 'var(--bg-secondary)/50',
}}
>
<div className="flex items-center justify-between text-sm" style={{ color: 'var(--text-secondary)' }}>
<span className="font-medium">
Mostrando <span className="font-bold text-[var(--text-primary)]">{filteredNotifications.length}</span> de <span className="font-bold text-[var(--text-primary)]">{notifications.length}</span> alertas
</span>
<div className="flex items-center gap-4">
{stats.unread > 0 && (
<span className="flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-[var(--color-info)] animate-pulse" />
<span className="font-semibold text-[var(--text-primary)]">{stats.unread}</span> sin leer
</span>
)}
{stats.snoozed > 0 && (
<span className="flex items-center gap-1.5">
<Clock className="w-3.5 h-3.5" />
<span className="font-semibold text-[var(--text-primary)]">{stats.snoozed}</span> pospuestas
</span>
)}
<div className="flex flex-col gap-3">
{/* Stats row */}
<div className="flex items-center justify-between text-sm" style={{ color: 'var(--text-secondary)' }}>
<span className="font-medium">
Mostrando <span className="font-bold text-[var(--text-primary)]">{startIndex + 1}-{Math.min(endIndex, totalAlerts)}</span> de <span className="font-bold text-[var(--text-primary)]">{totalAlerts}</span> alertas
</span>
<div className="flex items-center gap-4">
{stats.unread > 0 && (
<span className="flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-[var(--color-info)] animate-pulse" />
<span className="font-semibold text-[var(--text-primary)]">{stats.unread}</span> sin leer
</span>
)}
{stats.snoozed > 0 && (
<span className="flex items-center gap-1.5">
<Clock className="w-3.5 h-3.5" />
<span className="font-semibold text-[var(--text-primary)]">{stats.snoozed}</span> pospuestas
</span>
)}
</div>
</div>
{/* Pagination controls */}
{totalPages > 1 && (
<div className="flex items-center justify-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
className="h-8 px-3"
aria-label="Previous page"
>
<ChevronLeft className="w-4 h-4" />
</Button>
<span className="text-sm font-medium px-3" style={{ color: 'var(--text-primary)' }}>
Página <span className="font-bold">{currentPage}</span> de <span className="font-bold">{totalPages}</span>
</span>
<Button
variant="ghost"
size="sm"
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages}
className="h-8 px-3"
aria-label="Next page"
>
<ChevronRight className="w-4 h-4" />
</Button>
</div>
)}
</div>
</div>
)}