feat: update date range calculations and improve UI components for better consistency and readability

This commit is contained in:
Max Kowalski
2025-06-28 01:12:52 +02:00
parent c4cfe2f389
commit a002d5ef76
11 changed files with 314 additions and 220 deletions

View File

@ -128,9 +128,9 @@ export function SentimentChart({ sentimentData }: SentimentChartProps) {
sentimentData.negative,
],
backgroundColor: [
"rgba(34, 197, 94, 0.8)", // green
"rgba(249, 115, 22, 0.8)", // orange
"rgba(239, 68, 68, 0.8)", // red
"rgba(37, 99, 235, 0.8)", // blue (primary)
"rgba(107, 114, 128, 0.8)", // gray
"rgba(236, 72, 153, 0.8)", // pink
],
borderWidth: 1,
},
@ -196,12 +196,12 @@ export function LanguagePieChart({ languages }: LanguagePieChartProps) {
{
data,
backgroundColor: [
"rgba(59, 130, 246, 0.8)",
"rgba(16, 185, 129, 0.8)",
"rgba(249, 115, 22, 0.8)",
"rgba(236, 72, 153, 0.8)",
"rgba(139, 92, 246, 0.8)",
"rgba(107, 114, 128, 0.8)",
"rgba(37, 99, 235, 0.8)", // blue (primary)
"rgba(107, 114, 128, 0.8)", // gray
"rgba(236, 72, 153, 0.8)", // pink
"rgba(34, 197, 94, 0.8)", // lime green
"rgba(168, 85, 247, 0.8)", // purple
"rgba(251, 191, 36, 0.8)", // yellow
],
borderWidth: 1,
},

View File

@ -20,10 +20,10 @@ interface ResponseTimeDistributionProps {
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="rounded-lg border bg-background p-3 shadow-md">
<p className="text-sm font-medium">{label}</p>
<p className="text-sm text-muted-foreground">
<span className="font-medium text-foreground">
<div className="rounded-lg border border-gray-200 bg-white p-3 shadow-md">
<p className="text-sm font-medium text-gray-900">{label}</p>
<p className="text-sm text-gray-600">
<span className="font-medium text-gray-900">
{payload[0].value}
</span>{" "}
responses
@ -66,11 +66,11 @@ export default function ResponseTimeDistribution({
label = `${i}-${i + 1} sec`;
}
// Determine color based on response time
// Determine color based on response time using cohesive palette
let color;
if (i <= 2) color = "hsl(var(--chart-1))"; // Green for fast
else if (i <= 5) color = "hsl(var(--chart-4))"; // Yellow for medium
else color = "hsl(var(--chart-3))"; // Red for slow
if (i <= 2) color = "rgb(37, 99, 235)"; // Blue for fast (primary color)
else if (i <= 5) color = "rgb(107, 114, 128)"; // Gray for medium
else color = "rgb(236, 72, 153)"; // Pink for slow
return {
name: label,
@ -85,18 +85,18 @@ export default function ResponseTimeDistribution({
<BarChart data={chartData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid
strokeDasharray="3 3"
stroke="hsl(var(--border))"
strokeOpacity={0.3}
stroke="rgb(229, 231, 235)"
strokeOpacity={0.5}
/>
<XAxis
dataKey="name"
stroke="hsl(var(--muted-foreground))"
stroke="rgb(100, 116, 139)"
fontSize={12}
tickLine={false}
axisLine={false}
/>
<YAxis
stroke="hsl(var(--muted-foreground))"
stroke="rgb(100, 116, 139)"
fontSize={12}
tickLine={false}
axisLine={false}
@ -104,7 +104,7 @@ export default function ResponseTimeDistribution({
value: 'Number of Responses',
angle: -90,
position: 'insideLeft',
style: { textAnchor: 'middle' }
style: { textAnchor: 'middle', fill: 'rgb(100, 116, 139)' }
}}
/>
<Tooltip content={<CustomTooltip />} />
@ -122,14 +122,14 @@ export default function ResponseTimeDistribution({
{/* Average line */}
<ReferenceLine
x={Math.floor(average)}
stroke="hsl(var(--primary))"
stroke="rgb(0, 123, 255)"
strokeWidth={2}
strokeDasharray="5 5"
label={{
value: `Avg: ${average.toFixed(1)}s`,
position: "top" as const,
style: {
fill: "hsl(var(--primary))",
fill: "rgb(0, 123, 255)",
fontSize: "12px",
fontWeight: "500"
}
@ -140,14 +140,14 @@ export default function ResponseTimeDistribution({
{targetResponseTime && (
<ReferenceLine
x={Math.floor(targetResponseTime)}
stroke="hsl(var(--chart-2))"
stroke="rgb(255, 20, 147)"
strokeWidth={2}
strokeDasharray="3 3"
label={{
value: `Target: ${targetResponseTime}s`,
position: "top" as const,
style: {
fill: "hsl(var(--chart-2))",
fill: "rgb(255, 20, 147)",
fontSize: "12px",
fontWeight: "500"
}

View File

@ -11,9 +11,9 @@ interface TopQuestionsChartProps {
export default function TopQuestionsChart({ data, title = "Top 5 Asked Questions" }: TopQuestionsChartProps) {
if (!data || data.length === 0) {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold text-gray-900 mb-4">{title}</h3>
<div className="text-center py-8 text-gray-500">
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-lg font-semibold text-gray-900 mb-6">{title}</h3>
<div className="text-center py-12 text-gray-500">
No questions data available
</div>
</div>
@ -24,36 +24,38 @@ export default function TopQuestionsChart({ data, title = "Top 5 Asked Questions
const maxCount = Math.max(...data.map(q => q.count));
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold text-gray-900 mb-4">{title}</h3>
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-lg font-semibold text-gray-900 mb-6">{title}</h3>
<div className="space-y-4">
<div className="space-y-6">
{data.map((question, index) => {
const percentage = maxCount > 0 ? (question.count / maxCount) * 100 : 0;
return (
<div key={index} className="relative">
{/* Question text */}
<div className="flex justify-between items-start mb-2">
<p className="text-sm text-gray-700 font-medium leading-tight pr-4 flex-1">
{question.question}
</p>
<span className="text-sm font-semibold text-gray-900 bg-gray-100 px-2 py-1 rounded-md whitespace-nowrap">
{question.count}
</span>
</div>
{/* Progress bar */}
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300 ease-in-out"
style={{ width: `${percentage}%` }}
/>
</div>
{/* Rank indicator */}
<div className="absolute -left-2 top-0 w-6 h-6 bg-blue-600 text-white text-xs font-bold rounded-full flex items-center justify-center">
{index + 1}
<div key={index} className="group">
{/* Rank and Question */}
<div className="flex items-start gap-4 mb-3">
<div className="flex-shrink-0 w-8 h-8 bg-gray-100 text-gray-900 text-sm font-semibold rounded-full flex items-center justify-center">
{index + 1}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 leading-relaxed mb-2">
{question.question}
</p>
<div className="flex items-center justify-between">
<div className="flex-1 mr-4">
<div className="w-full bg-gray-100 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-500 ease-out"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
<span className="text-sm font-semibold text-gray-900 min-w-0">
{question.count} times
</span>
</div>
</div>
</div>
</div>
);
@ -61,10 +63,10 @@ export default function TopQuestionsChart({ data, title = "Top 5 Asked Questions
</div>
{/* Summary */}
<div className="mt-6 pt-4 border-t border-gray-200">
<div className="flex justify-between text-sm text-gray-600">
<span>Total questions analyzed</span>
<span className="font-medium">
<div className="mt-8 pt-6 border-t border-gray-100">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600">Total questions analyzed</span>
<span className="text-sm font-semibold text-gray-900">
{data.reduce((sum, q) => sum + q.count, 0)}
</span>
</div>

View File

@ -43,11 +43,11 @@ export default function ModernBarChart({
title,
dataKey = "value",
colors = [
"hsl(var(--chart-1))",
"hsl(var(--chart-2))",
"hsl(var(--chart-3))",
"hsl(var(--chart-4))",
"hsl(var(--chart-5))",
"rgb(37, 99, 235)", // Blue (primary)
"rgb(107, 114, 128)", // Gray
"rgb(236, 72, 153)", // Pink
"rgb(34, 197, 94)", // Lime green
"rgb(168, 85, 247)", // Purple
],
height = 300,
className,
@ -64,12 +64,12 @@ export default function ModernBarChart({
<BarChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid
strokeDasharray="3 3"
stroke="hsl(var(--border))"
strokeOpacity={0.3}
stroke="rgb(229, 231, 235)"
strokeOpacity={0.5}
/>
<XAxis
dataKey="name"
stroke="hsl(var(--muted-foreground))"
stroke="rgb(100, 116, 139)"
fontSize={12}
tickLine={false}
axisLine={false}
@ -78,7 +78,7 @@ export default function ModernBarChart({
height={80}
/>
<YAxis
stroke="hsl(var(--muted-foreground))"
stroke="rgb(100, 116, 139)"
fontSize={12}
tickLine={false}
axisLine={false}

View File

@ -67,11 +67,11 @@ export default function ModernDonutChart({
title,
centerText,
colors = [
"hsl(var(--chart-1))",
"hsl(var(--chart-2))",
"hsl(var(--chart-3))",
"hsl(var(--chart-4))",
"hsl(var(--chart-5))",
"rgb(37, 99, 235)", // Blue (primary)
"rgb(107, 114, 128)", // Gray
"rgb(236, 72, 153)", // Pink
"rgb(34, 197, 94)", // Lime green
"rgb(168, 85, 247)", // Purple
],
height = 300,
className,
@ -105,7 +105,7 @@ export default function ModernDonutChart({
key={`cell-${index}`}
fill={entry.color || colors[index % colors.length]}
className="hover:opacity-80 cursor-pointer"
stroke="hsl(var(--background))"
stroke="white"
strokeWidth={2}
/>
))}

View File

@ -44,7 +44,7 @@ export default function ModernLineChart({
data,
title,
dataKey = "value",
color = "hsl(var(--primary))",
color = "rgb(37, 99, 235)",
gradient = true,
height = 300,
className,
@ -71,18 +71,18 @@ export default function ModernLineChart({
</defs>
<CartesianGrid
strokeDasharray="3 3"
stroke="hsl(var(--border))"
strokeOpacity={0.3}
stroke="rgb(229, 231, 235)"
strokeOpacity={0.5}
/>
<XAxis
dataKey="date"
stroke="hsl(var(--muted-foreground))"
stroke="rgb(100, 116, 139)"
fontSize={12}
tickLine={false}
axisLine={false}
/>
<YAxis
stroke="hsl(var(--muted-foreground))"
stroke="rgb(100, 116, 139)"
fontSize={12}
tickLine={false}
axisLine={false}

View File

@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
"bg-white text-gray-900 flex flex-col gap-6 rounded-2xl border border-gray-100 py-6 shadow-sm",
className
)}
{...props}

View File

@ -51,31 +51,20 @@ export default function MetricCard({
const getVariantClasses = () => {
switch (variant) {
case "primary":
return "border-primary/20 bg-linear-to-br from-primary/5 to-primary/10";
return "border border-blue-100 bg-white shadow-sm hover:shadow-md";
case "success":
return "border-green-200 bg-linear-to-br from-green-50 to-green-100 dark:border-green-800 dark:from-green-950 dark:to-green-900";
return "border border-green-100 bg-white shadow-sm hover:shadow-md";
case "warning":
return "border-amber-200 bg-linear-to-br from-amber-50 to-amber-100 dark:border-amber-800 dark:from-amber-950 dark:to-amber-900";
return "border border-pink-100 bg-white shadow-sm hover:shadow-md";
case "danger":
return "border-red-200 bg-linear-to-br from-red-50 to-red-100 dark:border-red-800 dark:from-red-950 dark:to-red-900";
return "border border-red-100 bg-white shadow-sm hover:shadow-md";
default:
return "border-border bg-linear-to-br from-card to-muted/20";
return "border border-gray-100 bg-white shadow-sm hover:shadow-md";
}
};
const getIconClasses = () => {
switch (variant) {
case "primary":
return "bg-primary/10 text-primary border-primary/20";
case "success":
return "bg-green-100 text-green-600 border-green-200 dark:bg-green-900 dark:text-green-400 dark:border-green-800";
case "warning":
return "bg-amber-100 text-amber-600 border-amber-200 dark:bg-amber-900 dark:text-amber-400 dark:border-amber-800";
case "danger":
return "bg-red-100 text-red-600 border-red-200 dark:bg-red-900 dark:text-red-400 dark:border-red-800";
default:
return "bg-muted text-muted-foreground border-border";
}
return "bg-gray-50 text-gray-900 border-gray-100";
};
const getTrendIcon = () => {
@ -105,13 +94,11 @@ export default function MetricCard({
className
)}
>
{/* Subtle gradient overlay */}
<div className="absolute inset-0 bg-linear-to-br from-white/50 to-transparent dark:from-white/5 pointer-events-none" />
<CardHeader className="pb-3 relative">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground leading-none">
<p className="text-sm font-medium text-gray-900 leading-none">
{title}
</p>
{description && (
@ -137,7 +124,7 @@ export default function MetricCard({
<CardContent className="relative">
<div className="flex items-end justify-between">
<div className="space-y-1">
<p className="text-2xl font-bold tracking-tight">
<p className="text-2xl font-bold tracking-tight text-gray-900">
{value ?? "—"}
</p>