mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 06:32:10 +01:00
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:
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return lang; // Return original string for "Other" or invalid codes
|
// Use getLocalizedLanguageName for robust name resolution
|
||||||
|
// 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}`;
|
||||||
|
|||||||
@ -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();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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>{`
|
||||||
) : (
|
.leaflet-control-attribution {
|
||||||
<div className="h-full w-full bg-gray-100 flex items-center justify-center">
|
display: none !important;
|
||||||
No geographic data available
|
}
|
||||||
</div>
|
`}</style>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const config = {
|
const config = {
|
||||||
plugins: {
|
plugins: {
|
||||||
"@tailwindcss/postcss": {},
|
"@tailwindcss/postcss": {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
Reference in New Issue
Block a user