fix issues

This commit is contained in:
2026-01-01 15:17:09 +01:00
parent f001df3744
commit aee182b09e
17 changed files with 241 additions and 442 deletions

View File

@@ -78,6 +78,14 @@ export function AdminDashboardPage() {
return null;
}
// Temporary UI fields (not yet provided by API/ViewModel)
const adminCount = stats.systemAdmins;
const recentActivity: Array<{ description: string; timestamp: string; type: string }> = [];
const systemHealth = 'Healthy';
const totalSessions = 0;
const activeSessions = 0;
const avgSessionDuration = '—';
return (
<div className="container mx-auto p-6 space-y-6">
{/* Header */}
@@ -112,7 +120,7 @@ export function AdminDashboardPage() {
<div className="flex items-center justify-between">
<div>
<div className="text-sm text-gray-400 mb-1">Admins</div>
<div className="text-3xl font-bold text-white">{stats.adminCount}</div>
<div className="text-3xl font-bold text-white">{adminCount}</div>
</div>
<Shield className="w-8 h-8 text-purple-400" />
</div>
@@ -145,8 +153,8 @@ export function AdminDashboardPage() {
<Card>
<h3 className="text-lg font-semibold text-white mb-4">Recent Activity</h3>
<div className="space-y-3">
{stats.recentActivity.length > 0 ? (
stats.recentActivity.map((activity, index) => (
{recentActivity.length > 0 ? (
recentActivity.map((activity, index: number) => (
<div
key={index}
className="flex items-center justify-between p-3 bg-iron-gray/30 rounded-lg border border-charcoal-outline/50"
@@ -178,20 +186,20 @@ export function AdminDashboardPage() {
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">System Health</span>
<span className="px-2 py-1 text-xs rounded-full bg-performance-green/20 text-performance-green">
{stats.systemHealth}
{systemHealth}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Total Sessions</span>
<span className="text-white font-medium">{stats.totalSessions}</span>
<span className="text-white font-medium">{totalSessions}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Active Sessions</span>
<span className="text-white font-medium">{stats.activeSessions}</span>
<span className="text-white font-medium">{activeSessions}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Avg Session Duration</span>
<span className="text-white font-medium">{stats.avgSessionDuration}</span>
<span className="text-white font-medium">{avgSessionDuration}</span>
</div>
</div>
</Card>
@@ -214,4 +222,4 @@ export function AdminDashboardPage() {
</Card>
</div>
);
}
}

View File

@@ -70,6 +70,21 @@ export function AdminUsersPage() {
}
};
const toStatusBadgeProps = (
status: string,
): { status: 'success' | 'warning' | 'error' | 'neutral'; label: string } => {
switch (status) {
case 'active':
return { status: 'success', label: 'Active' };
case 'suspended':
return { status: 'warning', label: 'Suspended' };
case 'deleted':
return { status: 'error', label: 'Deleted' };
default:
return { status: 'neutral', label: status };
}
};
const handleDeleteUser = async (userId: string) => {
if (!confirm('Are you sure you want to delete this user? This action cannot be undone.')) {
return;
@@ -255,7 +270,10 @@ export function AdminUsersPage() {
</div>
</td>
<td className="py-3 px-4">
<StatusBadge status={user.statusBadge.label.toLowerCase()} />
{(() => {
const badge = toStatusBadgeProps(user.status);
return <StatusBadge status={badge.status} label={badge.label} />;
})()}
</td>
<td className="py-3 px-4">
<div className="text-sm text-gray-400">
@@ -338,4 +356,4 @@ export function AdminUsersPage() {
)}
</div>
);
}
}

View File

@@ -1,22 +1,21 @@
'use client';
import { ReactNode, useState } from 'react';
import { ReactNode } from 'react';
import { ChevronDown, ChevronUp } from 'lucide-react';
interface AccordionProps {
title: string;
icon: ReactNode;
children: ReactNode;
defaultOpen?: boolean;
isOpen: boolean;
onToggle: () => void;
}
export function Accordion({ title, icon, children, defaultOpen = false }: AccordionProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
export function Accordion({ title, icon, children, isOpen, onToggle }: AccordionProps) {
return (
<div className="border border-charcoal-outline rounded-lg overflow-hidden bg-iron-gray/30">
<button
onClick={() => setIsOpen(!isOpen)}
onClick={onToggle}
className="w-full flex items-center justify-between px-3 py-2 hover:bg-iron-gray/50 transition-colors"
>
<div className="flex items-center gap-2">

View File

@@ -38,6 +38,9 @@ export default function DevToolbar() {
const [circuitBreakers, setCircuitBreakers] = useState(() => CircuitBreakerRegistry.getInstance().getStatus());
const [checkingHealth, setCheckingHealth] = useState(false);
// Accordion state - only one open at a time
const [openAccordion, setOpenAccordion] = useState<string | null>('notifications');
const currentDriverId = useEffectiveDriverId();
// Sync login mode with actual session state on mount
@@ -350,17 +353,18 @@ export default function DevToolbar() {
{isExpanded && (
<div className="p-4 space-y-3">
{/* Notification Section - Accordion */}
<Accordion
title="Notifications"
<Accordion
title="Notifications"
icon={<MessageSquare className="w-4 h-4 text-gray-400" />}
defaultOpen={true}
isOpen={openAccordion === 'notifications'}
onToggle={() => setOpenAccordion(openAccordion === 'notifications' ? null : 'notifications')}
>
<div className="space-y-3">
<NotificationTypeSection
<NotificationTypeSection
selectedType={selectedType}
onSelectType={setSelectedType}
/>
<UrgencySection
<UrgencySection
selectedUrgency={selectedUrgency}
onSelectUrgency={setSelectedUrgency}
/>
@@ -375,10 +379,11 @@ export default function DevToolbar() {
</Accordion>
{/* API Status Section - Accordion */}
<Accordion
title="API Status"
<Accordion
title="API Status"
icon={<Activity className="w-4 h-4 text-gray-400" />}
defaultOpen={false}
isOpen={openAccordion === 'apiStatus'}
onToggle={() => setOpenAccordion(openAccordion === 'apiStatus' ? null : 'apiStatus')}
>
<APIStatusSection
apiStatus={apiStatus}
@@ -392,10 +397,11 @@ export default function DevToolbar() {
</Accordion>
{/* Login Section - Accordion */}
<Accordion
title="Demo Login"
<Accordion
title="Demo Login"
icon={<LogIn className="w-4 h-4 text-gray-400" />}
defaultOpen={false}
isOpen={openAccordion === 'login'}
onToggle={() => setOpenAccordion(openAccordion === 'login' ? null : 'login')}
>
<LoginSection
loginMode={loginMode}

View File

@@ -1,329 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import { ApiConnectionMonitor, ConnectionStatus } from '@/lib/api/base/ApiConnectionMonitor';
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
import {
Activity,
Wifi,
WifiOff,
AlertTriangle,
CheckCircle2,
RefreshCw,
Terminal,
Shield,
Clock,
TrendingUp
} from 'lucide-react';
interface ApiStatusToolbarProps {
position?: 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left';
autoHide?: boolean;
}
/**
* Development toolbar showing real-time API connection status
* Integrates with existing DevToolbar or works standalone
*/
export function ApiStatusToolbar({ position = 'bottom-right', autoHide = false }: ApiStatusToolbarProps) {
const [status, setStatus] = useState<ConnectionStatus>('disconnected');
const [health, setHealth] = useState(ApiConnectionMonitor.getInstance().getHealth());
const [expanded, setExpanded] = useState(false);
const [show, setShow] = useState(true);
useEffect(() => {
const monitor = ApiConnectionMonitor.getInstance();
const registry = CircuitBreakerRegistry.getInstance();
const updateState = () => {
setStatus(monitor.getStatus());
setHealth(monitor.getHealth());
};
// Initial state
updateState();
// Listen for events
monitor.on('connected', updateState);
monitor.on('disconnected', updateState);
monitor.on('degraded', updateState);
monitor.on('success', updateState);
monitor.on('failure', updateState);
// Auto-hide logic
if (autoHide) {
const hideTimer = setTimeout(() => setShow(false), 5000);
const showOnInteraction = () => setShow(true);
document.addEventListener('mousemove', showOnInteraction);
document.addEventListener('click', showOnInteraction);
return () => {
clearTimeout(hideTimer);
document.removeEventListener('mousemove', showOnInteraction);
document.removeEventListener('click', showOnInteraction);
monitor.off('connected', updateState);
monitor.off('disconnected', updateState);
monitor.off('degraded', updateState);
monitor.off('success', updateState);
monitor.off('failure', updateState);
};
}
return () => {
monitor.off('connected', updateState);
monitor.off('disconnected', updateState);
monitor.off('degraded', updateState);
monitor.off('success', updateState);
monitor.off('failure', updateState);
};
}, [autoHide]);
const handleHealthCheck = async () => {
const monitor = ApiConnectionMonitor.getInstance();
await monitor.performHealthCheck();
};
const handleReset = () => {
ApiConnectionMonitor.getInstance().reset();
CircuitBreakerRegistry.getInstance().resetAll();
};
const getReliabilityColor = (reliability: number) => {
if (reliability >= 95) return 'text-green-400';
if (reliability >= 80) return 'text-yellow-400';
return 'text-red-400';
};
const getStatusIcon = () => {
switch (status) {
case 'connected':
return <CheckCircle2 className="w-4 h-4 text-green-400" />;
case 'degraded':
return <AlertTriangle className="w-4 h-4 text-yellow-400" />;
case 'disconnected':
return <WifiOff className="w-4 h-4 text-red-400" />;
case 'checking':
return <RefreshCw className="w-4 h-4 animate-spin text-blue-400" />;
default:
return <Wifi className="w-4 h-4 text-gray-400" />;
}
};
const getStatusColor = () => {
switch (status) {
case 'connected': return 'bg-green-500/20 border-green-500/40';
case 'degraded': return 'bg-yellow-500/20 border-yellow-500/40';
case 'disconnected': return 'bg-red-500/20 border-red-500/40';
default: return 'bg-gray-500/20 border-gray-500/40';
}
};
const reliability = ((health.successfulRequests / Math.max(health.totalRequests, 1)) * 100).toFixed(1);
if (!show) {
return (
<button
onClick={() => setShow(true)}
className={`fixed p-2 bg-iron-gray border border-charcoal-outline rounded-lg shadow-lg hover:bg-charcoal-outline transition-all ${
position === 'bottom-right' ? 'bottom-4 right-4' :
position === 'top-right' ? 'top-4 right-4' :
position === 'bottom-left' ? 'bottom-4 left-4' :
'top-4 left-4'
}`}
>
<Activity className="w-5 h-5 text-primary-blue" />
</button>
);
}
return (
<div className={`fixed z-50 transition-all ${
position === 'bottom-right' ? 'bottom-4 right-4' :
position === 'top-right' ? 'top-4 right-4' :
position === 'bottom-left' ? 'bottom-4 left-4' :
'top-4 left-4'
}`}>
{/* Compact Status Indicator */}
{!expanded ? (
<button
onClick={() => setExpanded(true)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg border shadow-lg backdrop-blur-md transition-all hover:scale-105 ${getStatusColor()}`}
>
{getStatusIcon()}
<span className="text-sm font-semibold text-white">{status.toUpperCase()}</span>
<span className={`text-xs ${getReliabilityColor(parseFloat(reliability))}`}>
{reliability}%
</span>
</button>
) : (
/* Expanded Panel */
<div className={`w-80 rounded-lg border shadow-2xl backdrop-blur-md overflow-hidden ${getStatusColor()}`}>
{/* Header */}
<div className="bg-iron-gray/80 border-b border-charcoal-outline px-3 py-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-primary-blue" />
<span className="text-xs font-bold text-white">API STATUS</span>
</div>
<div className="flex gap-1">
<button
onClick={handleHealthCheck}
className="p-1 hover:bg-charcoal-outline rounded transition-colors"
title="Run Health Check"
>
<RefreshCw className="w-3 h-3 text-gray-400 hover:text-white" />
</button>
<button
onClick={handleReset}
className="p-1 hover:bg-charcoal-outline rounded transition-colors"
title="Reset Stats"
>
<span className="text-xs text-gray-400 hover:text-white">R</span>
</button>
<button
onClick={() => setExpanded(false)}
className="p-1 hover:bg-charcoal-outline rounded transition-colors"
>
<span className="text-xs text-gray-400 hover:text-white">×</span>
</button>
</div>
</div>
{/* Body */}
<div className="px-3 py-2 space-y-2 bg-deep-graphite/90">
{/* Status Row */}
<div className="flex items-center justify-between">
<span className="text-xs text-gray-400">Status</span>
<span className={`text-xs font-bold uppercase ${status === 'connected' ? 'text-green-400' : status === 'degraded' ? 'text-yellow-400' : 'text-red-400'}`}>
{status}
</span>
</div>
{/* Reliability */}
<div className="flex items-center justify-between">
<span className="text-xs text-gray-400">Reliability</span>
<span className={`text-xs font-bold ${getReliabilityColor(parseFloat(reliability))}`}>
{reliability}%
</span>
</div>
{/* Request Stats */}
<div className="grid grid-cols-3 gap-2 text-center">
<div className="bg-iron-gray/50 rounded p-1">
<div className="text-[10px] text-gray-400">Total</div>
<div className="text-sm font-bold text-white">{health.totalRequests}</div>
</div>
<div className="bg-iron-gray/50 rounded p-1">
<div className="text-[10px] text-gray-400">Success</div>
<div className="text-sm font-bold text-green-400">{health.successfulRequests}</div>
</div>
<div className="bg-iron-gray/50 rounded p-1">
<div className="text-[10px] text-gray-400">Failed</div>
<div className="text-sm font-bold text-red-400">{health.failedRequests}</div>
</div>
</div>
{/* Performance */}
<div className="flex items-center justify-between">
<span className="text-xs text-gray-400">Avg Response</span>
<span className="text-xs font-mono text-blue-400">
{health.averageResponseTime.toFixed(0)}ms
</span>
</div>
{/* Consecutive Failures */}
{health.consecutiveFailures > 0 && (
<div className="flex items-center justify-between bg-red-500/10 rounded px-2 py-1">
<span className="text-xs text-red-400">Consecutive Failures</span>
<span className="text-xs font-bold text-red-400">{health.consecutiveFailures}</span>
</div>
)}
{/* Circuit Breakers */}
<div className="border-t border-charcoal-outline pt-2">
<div className="flex items-center gap-1 mb-1">
<Shield className="w-3 h-3 text-gray-400" />
<span className="text-[10px] text-gray-400 font-bold">CIRCUIT BREAKERS</span>
</div>
<CircuitBreakerStatus />
</div>
{/* Last Check */}
<div className="border-t border-charcoal-outline pt-2 flex items-center justify-between">
<span className="text-[10px] text-gray-500">Last Check</span>
<span className="text-[10px] text-gray-400 font-mono">
{health.lastCheck ? new Date(health.lastCheck).toLocaleTimeString() : 'Never'}
</span>
</div>
{/* Actions */}
<div className="grid grid-cols-2 gap-2 pt-1">
<button
onClick={handleHealthCheck}
className="px-2 py-1 bg-primary-blue hover:bg-primary-blue/80 text-white text-xs rounded transition-colors"
>
Check Health
</button>
<button
onClick={() => {
const monitor = ApiConnectionMonitor.getInstance();
const report = monitor.getDebugReport();
alert(report);
}}
className="px-2 py-1 bg-iron-gray hover:bg-charcoal-outline text-gray-300 text-xs rounded transition-colors border border-charcoal-outline"
>
Debug Report
</button>
</div>
</div>
</div>
)}
</div>
);
}
/**
* Circuit Breaker Status Component
*/
function CircuitBreakerStatus() {
const [status, setStatus] = useState(CircuitBreakerRegistry.getInstance().getStatus());
useEffect(() => {
const registry = CircuitBreakerRegistry.getInstance();
// Poll for updates every 2 seconds
const interval = setInterval(() => {
setStatus(registry.getStatus());
}, 2000);
return () => clearInterval(interval);
}, []);
const entries = Object.entries(status);
if (entries.length === 0) {
return (
<div className="text-[10px] text-gray-500 italic">No active circuit breakers</div>
);
}
return (
<div className="space-y-1 max-h-20 overflow-auto">
{entries.map(([endpoint, breaker]) => (
<div key={endpoint} className="flex items-center justify-between text-[10px]">
<span className="text-gray-400 truncate flex-1">{endpoint.split('/').pop() || endpoint}</span>
<span className={`px-1 rounded ${
breaker.state === 'CLOSED' ? 'bg-green-500/20 text-green-400' :
breaker.state === 'OPEN' ? 'bg-red-500/20 text-red-400' :
'bg-yellow-500/20 text-yellow-400'
}`}>
{breaker.state}
</span>
{breaker.failures > 0 && (
<span className="text-red-400 ml-1">({breaker.failures})</span>
)}
</div>
))}
</div>
);
}