mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 07:52:10 +01:00
feat: implement comprehensive enum formatting system
- Create centralized enum formatting utility for database enums - Transform raw enums to human-readable text (SALARY_COMPENSATION → Salary & Compensation) - Apply formatting across sessions list, individual session pages, and charts - Improve color contrast ratios for better WCAG compliance - Add semantic list structure with proper article elements - Enhance accessibility with proper ARIA labels and screen reader support - Fix all instances where users saw ugly database enums in UI
This commit is contained in:
@ -4,6 +4,7 @@ import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Company, MetricsResult, WordCloudWord } from "../../../lib/types";
|
||||
import { formatEnumValue } from "@/lib/format-enums";
|
||||
import MetricCard from "../../../components/ui/metric-card";
|
||||
import ModernLineChart from "../../../components/charts/line-chart";
|
||||
import ModernBarChart from "../../../components/charts/bar-chart";
|
||||
@ -259,10 +260,13 @@ function DashboardContent() {
|
||||
const getCategoriesData = () => {
|
||||
if (!metrics?.categories) return [];
|
||||
|
||||
return Object.entries(metrics.categories).map(([name, value]) => ({
|
||||
name: name.length > 15 ? name.substring(0, 15) + "..." : name,
|
||||
value: value as number,
|
||||
}));
|
||||
return Object.entries(metrics.categories).map(([name, value]) => {
|
||||
const formattedName = formatEnumValue(name) || name;
|
||||
return {
|
||||
name: formattedName.length > 15 ? formattedName.substring(0, 15) + "..." : formattedName,
|
||||
value: value as number,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const getLanguagesData = () => {
|
||||
|
||||
@ -7,6 +7,7 @@ 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";
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@ -181,10 +182,10 @@ export default function SessionViewPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{session.category && session.category !== 'UNRECOGNIZED_OTHER' && session.category !== 'ACCESS_LOGIN' && (
|
||||
{session.category && (
|
||||
<Badge variant="secondary" className="gap-1">
|
||||
<Activity className="h-3 w-3" />
|
||||
{session.category.replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase())}
|
||||
{formatCategory(session.category)}
|
||||
</Badge>
|
||||
)}
|
||||
{session.language && (
|
||||
|
||||
@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { formatCategory } from "@/lib/format-enums";
|
||||
import {
|
||||
MessageSquare,
|
||||
Search,
|
||||
@ -223,7 +224,7 @@ export default function SessionsPage() {
|
||||
<option value="">All Categories</option>
|
||||
{filterOptions.categories.map((cat) => (
|
||||
<option key={cat} value={cat}>
|
||||
{cat}
|
||||
{formatCategory(cat)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@ -377,69 +378,81 @@ export default function SessionsPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Sessions List */}
|
||||
{!loading && !error && sessions.length > 0 && (
|
||||
<div className="grid gap-4">
|
||||
{sessions.map((session) => (
|
||||
<Card key={session.id} className="hover:shadow-md transition-shadow">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="space-y-2 flex-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
ID
|
||||
</Badge>
|
||||
<code className="text-sm text-muted-foreground font-mono truncate max-w-24">
|
||||
{session.sessionId || session.id}
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
{new Date(session.startTime).toLocaleDateString()}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(session.startTime).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href={`/dashboard/sessions/${session.id}`}>
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<Eye className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">View Details</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{/* Sessions List */}
|
||||
{!loading && !error && sessions.length > 0 && (
|
||||
<ul role="list" aria-label="Chat sessions" className="grid gap-4">
|
||||
{sessions.map((session) => (
|
||||
<li key={session.id}>
|
||||
<Card className="hover:shadow-md transition-shadow">
|
||||
<CardContent className="pt-6">
|
||||
<article aria-labelledby={`session-${session.id}-title`}>
|
||||
<header className="flex justify-between items-start mb-4">
|
||||
<div className="space-y-2 flex-1">
|
||||
<h3 id={`session-${session.id}-title`} className="sr-only">
|
||||
Session {session.sessionId || session.id} from {new Date(session.startTime).toLocaleDateString()}
|
||||
</h3>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
ID
|
||||
</Badge>
|
||||
<code className="text-sm text-muted-foreground font-mono truncate max-w-24">
|
||||
{session.sessionId || session.id}
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Clock className="h-3 w-3 mr-1" aria-hidden="true" />
|
||||
{new Date(session.startTime).toLocaleDateString()}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(session.startTime).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href={`/dashboard/sessions/${session.id}`}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
aria-label={`View details for session ${session.sessionId || session.id}`}
|
||||
>
|
||||
<Eye className="h-4 w-4" aria-hidden="true" />
|
||||
<span className="hidden sm:inline">View Details</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{session.category && session.category !== 'UNRECOGNIZED_OTHER' && session.category !== 'ACCESS_LOGIN' && (
|
||||
<Badge variant="secondary" className="gap-1">
|
||||
<Filter className="h-3 w-3" />
|
||||
{session.category.replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase())}
|
||||
</Badge>
|
||||
)}
|
||||
{session.language && (
|
||||
<Badge variant="outline" className="gap-1">
|
||||
<Globe className="h-3 w-3" />
|
||||
{session.language.toUpperCase()}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{session.category && (
|
||||
<Badge variant="secondary" className="gap-1">
|
||||
<Filter className="h-3 w-3" aria-hidden="true" />
|
||||
{formatCategory(session.category)}
|
||||
</Badge>
|
||||
)}
|
||||
{session.language && (
|
||||
<Badge variant="outline" className="gap-1">
|
||||
<Globe className="h-3 w-3" aria-hidden="true" />
|
||||
{session.language.toUpperCase()}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{session.summary ? (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{session.summary}
|
||||
</p>
|
||||
) : session.initialMsg ? (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{session.initialMsg}
|
||||
</p>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{session.summary ? (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{session.summary}
|
||||
</p>
|
||||
) : session.initialMsg ? (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{session.initialMsg}
|
||||
</p>
|
||||
) : null}
|
||||
</article>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 0 && (
|
||||
|
||||
@ -105,13 +105,13 @@
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--muted-foreground: oklch(0.45 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--accent-foreground: oklch(0.15 0 0);
|
||||
--destructive: oklch(0.55 0.245 27.325);
|
||||
--border: oklch(0.85 0 0);
|
||||
--input: oklch(0.85 0 0);
|
||||
--ring: oklch(0.6 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
@ -139,13 +139,13 @@
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.65 0 0);
|
||||
--muted-foreground: oklch(0.75 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 15%);
|
||||
--input: oklch(1 0 0 / 20%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--destructive: oklch(0.75 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 25%);
|
||||
--input: oklch(1 0 0 / 30%);
|
||||
--ring: oklch(0.75 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
|
||||
Reference in New Issue
Block a user