mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 18:52:08 +01:00
fix: address multiple PR review issues
- Fixed accessibility in audit logs with keyboard navigation and ARIA attributes - Refactored ThreatAnalysisResults interface to module level for reusability - Added BatchOperation enum validation and proper CSV escaping in batch monitoring - Removed unused company state causing skeleton view in dashboard overview - Enhanced error handling with user-facing messages for metrics loading - Replaced hardcoded timeouts with condition-based waits in E2E tests - Removed duplicate state management in security monitoring hooks - Fixed CSRF documentation to show proper secret fallback pattern - Updated CSP metrics docs with GDPR Article 6(1)(f) legal basis clarification - Fixed React hooks order to prevent conditional execution after early returns - Added explicit button type to prevent form submission behavior
This commit is contained in:
@ -184,7 +184,10 @@ export default function AuditLogsPage() {
|
||||
}, [session?.user?.role, hasFetched, fetchAuditLogs]);
|
||||
|
||||
// Function to refresh audit logs (for filter changes)
|
||||
const refreshAuditLogs = useCallback(() => {
|
||||
const refreshAuditLogs = useCallback((newPage?: number) => {
|
||||
if (newPage !== undefined) {
|
||||
setPagination((prev) => ({ ...prev, page: newPage }));
|
||||
}
|
||||
setHasFetched(false);
|
||||
}, []);
|
||||
|
||||
@ -445,8 +448,8 @@ export default function AuditLogsPage() {
|
||||
size="sm"
|
||||
disabled={!pagination.hasPrev}
|
||||
onClick={() => {
|
||||
setPagination((prev) => ({ ...prev, page: prev.page - 1 }));
|
||||
refreshAuditLogs();
|
||||
const newPage = pagination.page - 1;
|
||||
refreshAuditLogs(newPage);
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
@ -456,8 +459,8 @@ export default function AuditLogsPage() {
|
||||
size="sm"
|
||||
disabled={!pagination.hasNext}
|
||||
onClick={() => {
|
||||
setPagination((prev) => ({ ...prev, page: prev.page + 1 }));
|
||||
refreshAuditLogs();
|
||||
const newPage = pagination.page + 1;
|
||||
refreshAuditLogs(newPage);
|
||||
}}
|
||||
>
|
||||
Next
|
||||
|
||||
@ -470,7 +470,7 @@ function DashboardContent() {
|
||||
const { data: session, status } = useSession();
|
||||
const router = useRouter();
|
||||
const [metrics, setMetrics] = useState<MetricsResult | null>(null);
|
||||
const [company] = useState<Company | null>(null);
|
||||
// Remove unused company state that was causing skeleton view to always show
|
||||
const [refreshing, setRefreshing] = useState<boolean>(false);
|
||||
const [isInitialLoad, setIsInitialLoad] = useState<boolean>(true);
|
||||
|
||||
@ -501,12 +501,39 @@ function DashboardContent() {
|
||||
// Map overview data to metrics format expected by the component
|
||||
const mappedMetrics: Partial<MetricsResult> = {
|
||||
totalSessions: overviewData.totalSessions,
|
||||
avgSessionsPerDay: 0, // Will be computed properly later
|
||||
avgSessionLength: null,
|
||||
days: {},
|
||||
languages: {},
|
||||
countries: {},
|
||||
belowThresholdCount: 0,
|
||||
avgSessionsPerDay: overviewData.avgSessionsPerDay || 0,
|
||||
avgSessionLength: overviewData.avgSessionLength || 0,
|
||||
days:
|
||||
overviewData.timeSeriesData?.reduce(
|
||||
(acc, item) => {
|
||||
if (item.date) {
|
||||
acc[item.date] = item.sessionCount || 0;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
) || {},
|
||||
languages:
|
||||
overviewData.languageDistribution?.reduce(
|
||||
(acc, item) => {
|
||||
if (item.language) {
|
||||
acc[item.language] = item.count;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
) || {},
|
||||
countries:
|
||||
overviewData.geographicDistribution?.reduce(
|
||||
(acc, item) => {
|
||||
if (item.country) {
|
||||
acc[item.country] = item.count;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
) || {},
|
||||
belowThresholdCount: overviewData.belowThresholdCount || 0,
|
||||
// Map sentiment data to individual counts
|
||||
sentimentPositiveCount:
|
||||
overviewData.sentimentDistribution?.find(
|
||||
@ -541,12 +568,6 @@ function DashboardContent() {
|
||||
}
|
||||
}, [overviewData, isInitialLoad]);
|
||||
|
||||
useEffect(() => {
|
||||
if (metricsError) {
|
||||
console.error("Error fetching metrics:", metricsError);
|
||||
}
|
||||
}, [metricsError]);
|
||||
|
||||
// Admin refresh sessions mutation
|
||||
const refreshSessionsMutation = trpc.admin.refreshSessions.useMutation({
|
||||
onSuccess: () => {
|
||||
@ -567,6 +588,30 @@ function DashboardContent() {
|
||||
// tRPC queries handle data fetching automatically
|
||||
}, [status, router]);
|
||||
|
||||
// Enhanced error handling with user feedback
|
||||
if (metricsError) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="text-red-600 text-lg font-semibold">
|
||||
Failed to load dashboard data
|
||||
</div>
|
||||
<p className="text-gray-600">
|
||||
There was an error loading your dashboard metrics. Please try
|
||||
refreshing the page.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||
>
|
||||
Refresh Page
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
if (isAuditor) return;
|
||||
|
||||
@ -594,14 +639,14 @@ function DashboardContent() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!metrics || !company) {
|
||||
if (!metrics) {
|
||||
return <DashboardSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<DashboardHeader
|
||||
company={company}
|
||||
company={{ name: "Analytics Dashboard" } as Company}
|
||||
metrics={metrics}
|
||||
isAuditor={isAuditor}
|
||||
refreshing={refreshing}
|
||||
|
||||
Reference in New Issue
Block a user