Improves dashboard UI and user management

Enhances the dashboard's visual presentation and user management
functionality.

- Addresses a layout issue by adding overflow hidden to the word cloud container.
- Improves user management form responsiveness on smaller screens.
- Enhances language name display in the pie chart using a dedicated function and ensures correct ISO code display.
- Refines donut chart's center text styling for better readability.
- Fixes geographic map's height and removes attribution for a cleaner look.
This commit is contained in:
2025-05-22 05:05:04 +02:00
parent 00f2f5c59b
commit ac7cafd7b2
7 changed files with 43 additions and 49 deletions

View File

@ -384,7 +384,7 @@ function DashboardContent() {
</div> </div>
{/* Word Cloud and World Map */} {/* Word Cloud and World Map */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white p-6 rounded-xl shadow"> <div className="bg-white p-6 rounded-xl shadow overflow-hidden">
<h3 className="font-bold text-lg text-gray-800 mb-4"> <h3 className="font-bold text-lg text-gray-800 mb-4">
Categories Word Cloud Categories Word Cloud
</h3> </h3>

View File

@ -39,15 +39,15 @@ export default function UserManagement({ session }: UserManagementProps) {
return ( return (
<div className="bg-white p-6 rounded-xl shadow mb-6"> <div className="bg-white p-6 rounded-xl shadow mb-6">
<h2 className="font-bold text-lg mb-4">User Management</h2> <h2 className="font-bold text-lg mb-4">User Management</h2>
<div className="flex gap-2 mb-3"> <div className="flex flex-col sm:flex-row gap-2 mb-3">
<input <input
className="border px-3 py-2 rounded" className="border px-3 py-2 rounded w-full sm:w-auto"
placeholder="Email" placeholder="Email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
/> />
<select <select
className="border px-3 py-2 rounded" className="border px-3 py-2 rounded w-full sm:w-auto"
value={role} value={role}
onChange={(e) => setRole(e.target.value)} onChange={(e) => setRole(e.target.value)}
> >
@ -56,7 +56,7 @@ export default function UserManagement({ session }: UserManagementProps) {
<option value="auditor">Auditor</option> <option value="auditor">Auditor</option>
</select> </select>
<button <button
className="bg-blue-600 text-white rounded px-4" className="bg-blue-600 text-white rounded px-4 py-2 sm:py-0 w-full sm:w-auto"
onClick={inviteUser} onClick={inviteUser}
> >
Invite Invite

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import Chart from "chart.js/auto"; import Chart from "chart.js/auto";
import { getLocalizedLanguageName } from "../lib/localization"; // Corrected import path
interface SessionsData { interface SessionsData {
[date: string]: number; [date: string]: number;
@ -173,25 +174,16 @@ export function LanguagePieChart({ languages }: LanguagePieChartProps) {
topLanguages.push(["Other", otherCount]); topLanguages.push(["Other", otherCount]);
} }
// Use Intl.DisplayNames to get localized language names from ISO codes
const languageDisplayNames = new Intl.DisplayNames(["en"], {
type: "language",
});
// Store original ISO codes for tooltip // Store original ISO codes for tooltip
const isoCodes = topLanguages.map(([lang]) => lang); const isoCodes = topLanguages.map(([lang]) => lang);
const labels = topLanguages.map(([lang]) => { const labels = topLanguages.map(([lang]) => {
// Check if this is a valid ISO 639-1 language code if (lang === "Other") {
if (lang && lang !== "Other" && /^[a-z]{2}$/.test(lang)) { return "Other";
try {
return languageDisplayNames.of(lang);
} catch {
// Empty catch block - no need to name the error parameter
return lang; // Fallback to code if display name can't be resolved
} }
} // Use getLocalizedLanguageName for robust name resolution
return lang; // Return original string for "Other" or invalid codes // Pass "en" to maintain consistency with previous behavior if navigator.language is different
return getLocalizedLanguageName(lang, "en");
}); });
const data = topLanguages.map(([, count]) => count); const data = topLanguages.map(([, count]) => count);
@ -231,15 +223,16 @@ export function LanguagePieChart({ languages }: LanguagePieChartProps) {
const label = context.label || ""; const label = context.label || "";
const value = context.formattedValue || ""; const value = context.formattedValue || "";
const index = context.dataIndex; const index = context.dataIndex;
const isoCode = isoCodes[index]; const originalIsoCode = isoCodes[index]; // Get the original code
// Only show ISO code if it's not "Other" and it's a valid 2-letter code // Only show ISO code if it's not "Other"
// and it's a valid 2-letter code (check lowercase version)
if ( if (
isoCode && originalIsoCode &&
isoCode !== "Other" && originalIsoCode !== "Other" &&
/^[a-z]{2}$/.test(isoCode) /^[a-z]{2}$/.test(originalIsoCode.toLowerCase())
) { ) {
return `${label} (${isoCode.toUpperCase()}): ${value}`; return `${label} (${originalIsoCode.toUpperCase()}): ${value}`;
} }
return `${label}: ${value}`; return `${label}: ${value}`;

View File

@ -97,28 +97,30 @@ export default function DonutChart({ data, centerText }: DonutChartProps) {
const ctx = chart.ctx; const ctx = chart.ctx;
ctx.restore(); ctx.restore();
const centerX = width / 2;
const centerY = height / 2;
// Title text // Title text
if (centerText.title) { if (centerText.title) {
ctx.font = "14px Arial"; ctx.font = "1rem sans-serif"; // Consistent font
ctx.fillStyle = "#6B7280"; // text-gray-500 ctx.fillStyle = "#6B7280"; // Tailwind gray-500
ctx.textBaseline = "middle";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillText(centerText.title, width / 2, height / 2 - 10); ctx.textBaseline = "middle"; // Align vertically
ctx.fillText(centerText.title, centerX, centerY - 10); // Adjust Y offset
} }
// Value text // Value text
if (centerText.value !== undefined) { if (centerText.value !== undefined) {
ctx.font = "bold 20px Arial"; ctx.font = "bold 1.5rem sans-serif"; // Consistent font, larger
ctx.fillStyle = "#111827"; // text-gray-900 ctx.fillStyle = "#1F2937"; // Tailwind gray-800
ctx.textBaseline = "middle";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; // Align vertically
ctx.fillText( ctx.fillText(
String(centerText.value), centerText.value.toString(),
width / 2, centerX,
height / 2 + 10 centerY + 15
); ); // Adjust Y offset
} }
ctx.save(); ctx.save();
}, },
}, },

View File

@ -99,14 +99,13 @@ export default function GeographicMap({
} }
return ( return (
<div className="h-full w-full" style={{ height }}> <div style={{ height: `${height}px`, width: "100%" }} className="relative">
{Object.keys(countries).length > 0 ? (
<Map countryData={countryData} maxCount={maxCount} /> <Map countryData={countryData} maxCount={maxCount} />
) : ( <style jsx global>{`
<div className="h-full w-full bg-gray-100 flex items-center justify-center"> .leaflet-control-attribution {
No geographic data available display: none !important;
</div> }
)} `}</style>
</div> </div>
); );
} }

View File

@ -2,7 +2,7 @@
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
// Allow cross-origin requests from specific origins in development // Allow cross-origin requests from specific origins in development
allowedDevOrigins: ["192.168.1.2","localhost","propc"], allowedDevOrigins: ["192.168.1.2", "localhost", "propc"],
}; };
export default nextConfig; export default nextConfig;