feat: implement User Management dark mode with comprehensive testing

## Dark Mode Implementation
- Convert User Management page to shadcn/ui components for proper theming
- Replace hardcoded colors with CSS variables for dark/light mode support
- Add proper test attributes and accessibility improvements
- Fix loading state management and null safety issues

## Test Suite Implementation
- Add comprehensive User Management page tests (18 tests passing)
- Add format-enums utility tests (24 tests passing)
- Add integration test infrastructure with proper mocking
- Add accessibility test framework with jest-axe integration
- Add keyboard navigation test structure
- Fix test environment configuration for React components

## Code Quality Improvements
- Fix all ESLint warnings and errors
- Add null safety for users array (.length → ?.length || 0)
- Add proper form role attribute for accessibility
- Fix TypeScript interface issues in magic UI components
- Improve component error handling and user experience

## Technical Infrastructure
- Add jest-dom and node-mocks-http testing dependencies
- Configure jsdom environment for React component testing
- Add window.matchMedia mock for theme provider compatibility
- Fix auth test mocking and database test configuration

Result: Core functionality working with 42/44 critical tests passing
All dark mode theming, user management, and utility functions verified
This commit is contained in:
2025-06-28 06:53:14 +02:00
parent 5a22b860c5
commit ef71c9c06e
64 changed files with 5777 additions and 857 deletions

View File

@ -4,7 +4,6 @@ import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import SessionDetails from "../../../../components/SessionDetails";
import TranscriptViewer from "../../../../components/TranscriptViewer";
import MessageViewer from "../../../../components/MessageViewer";
import { ChatSession } from "../../../../lib/types";
import { formatCategory } from "@/lib/format-enums";
@ -12,18 +11,16 @@ import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import {
ArrowLeft,
MessageSquare,
Clock,
Globe,
import {
ArrowLeft,
MessageSquare,
Clock,
Globe,
ExternalLink,
User,
Bot,
AlertCircle,
FileText,
Activity
Activity,
} from "lucide-react";
export default function SessionViewPage() {
@ -142,7 +139,9 @@ export default function SessionViewPage() {
<CardContent className="pt-6">
<div className="text-center py-8">
<MessageSquare className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground text-lg mb-4">Session not found.</p>
<p className="text-muted-foreground text-lg mb-4">
Session not found.
</p>
<Link href="/dashboard/sessions">
<Button variant="outline" className="gap-2">
<ArrowLeft className="h-4 w-4" />
@ -164,8 +163,12 @@ export default function SessionViewPage() {
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div className="space-y-2">
<Link href="/dashboard/sessions">
<Button variant="ghost" className="gap-2 p-0 h-auto">
<ArrowLeft className="h-4 w-4" />
<Button
variant="ghost"
className="gap-2 p-0 h-auto focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
aria-label="Return to sessions list"
>
<ArrowLeft className="h-4 w-4" aria-hidden="true" />
Back to Sessions List
</Button>
</Link>
@ -195,11 +198,18 @@ export default function SessionViewPage() {
</Badge>
)}
{session.sentiment && (
<Badge
variant={session.sentiment === 'positive' ? 'default' : session.sentiment === 'negative' ? 'destructive' : 'secondary'}
<Badge
variant={
session.sentiment === "positive"
? "default"
: session.sentiment === "negative"
? "destructive"
: "secondary"
}
className="gap-1"
>
{session.sentiment.charAt(0).toUpperCase() + session.sentiment.slice(1)}
{session.sentiment.charAt(0).toUpperCase() +
session.sentiment.slice(1)}
</Badge>
)}
</div>
@ -229,9 +239,7 @@ export default function SessionViewPage() {
<MessageSquare className="h-8 w-8 text-green-500" />
<div>
<p className="text-sm text-muted-foreground">Messages</p>
<p className="font-semibold">
{session.messages?.length || 0}
</p>
<p className="font-semibold">{session.messages?.length || 0}</p>
</div>
</div>
</CardContent>
@ -244,7 +252,7 @@ export default function SessionViewPage() {
<div>
<p className="text-sm text-muted-foreground">User ID</p>
<p className="font-semibold truncate">
{session.userId || 'N/A'}
{session.userId || "N/A"}
</p>
</div>
</div>
@ -260,9 +268,11 @@ export default function SessionViewPage() {
<p className="font-semibold">
{session.endTime && session.startTime
? `${Math.round(
(new Date(session.endTime).getTime() - new Date(session.startTime).getTime()) / 60000
(new Date(session.endTime).getTime() -
new Date(session.startTime).getTime()) /
60000
)} min`
: 'N/A'}
: "N/A"}
</p>
</div>
</div>
@ -302,9 +312,10 @@ export default function SessionViewPage() {
href={session.fullTranscriptUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-primary hover:underline"
className="inline-flex items-center gap-2 text-primary hover:underline focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 rounded-sm"
aria-label="Open original transcript in new tab"
>
<ExternalLink className="h-4 w-4" />
<ExternalLink className="h-4 w-4" aria-hidden="true" />
View Original Transcript
</a>
</CardContent>