mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 08:52:10 +01:00
Refactors the dashboard to improve data fetching, error handling, and overall user experience. - Prevents errors on refresh by validating company ID. - Improves date handling from CSV by using a `safeParseDate` function to avoid "Invalid Date" errors. - Adds a timestamp for when metrics were last updated. - Fixes a bug where the refresh was failing silently. - Improves settings page by wrapping form elements with form tags. - Adds autocomplete attributes on settings page. - Adds database files to `.gitignore`.
166 lines
5.1 KiB
TypeScript
166 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { signOut, useSession } from "next-auth/react";
|
|
import { SessionsLineChart, CategoriesBarChart } from "../../components/Charts";
|
|
import DashboardSettings from "./settings";
|
|
import UserManagement from "./users";
|
|
import { Company, MetricsResult } from "../../lib/types";
|
|
|
|
interface MetricsCardProps {
|
|
label: string;
|
|
value: string | number | null | undefined;
|
|
}
|
|
|
|
function MetricsCard({ label, value }: MetricsCardProps) {
|
|
return (
|
|
<div className="bg-white rounded-xl p-4 shadow-md flex flex-col items-center">
|
|
<span className="text-2xl font-bold">{value ?? "-"}</span>
|
|
<span className="text-gray-500">{label}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Safely wrapped component with useSession
|
|
function DashboardContent() {
|
|
const { data: session } = useSession();
|
|
const [metrics, setMetrics] = useState<MetricsResult | null>(null);
|
|
const [company, setCompany] = useState<Company | null>(null);
|
|
const [, setLoading] = useState<boolean>(false);
|
|
// Remove unused csvUrl state variable
|
|
const [refreshing, setRefreshing] = useState<boolean>(false);
|
|
|
|
const isAdmin = session?.user?.role === "admin";
|
|
const isAuditor = session?.user?.role === "auditor";
|
|
|
|
useEffect(() => {
|
|
// Fetch metrics and company on mount
|
|
const fetchData = async () => {
|
|
setLoading(true);
|
|
const res = await fetch("/api/dashboard/metrics");
|
|
const data = await res.json();
|
|
setMetrics(data.metrics);
|
|
setCompany(data.company);
|
|
// Removed unused csvUrl assignment
|
|
setLoading(false);
|
|
};
|
|
fetchData();
|
|
}, []);
|
|
|
|
async function handleRefresh() {
|
|
if (isAuditor) return; // Prevent auditors from refreshing
|
|
try {
|
|
setRefreshing(true);
|
|
|
|
// Make sure we have a company ID to send
|
|
if (!company?.id) {
|
|
console.error("Cannot refresh: Company ID is missing");
|
|
return;
|
|
}
|
|
|
|
const res = await fetch("/api/admin/refresh-sessions", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ companyId: company.id }),
|
|
});
|
|
|
|
if (res.ok) {
|
|
// Refetch metrics
|
|
const metricsRes = await fetch("/api/dashboard/metrics");
|
|
const data = await metricsRes.json();
|
|
setMetrics(data.metrics);
|
|
} else {
|
|
const errorData = await res.json();
|
|
console.error("Failed to refresh sessions:", errorData.error);
|
|
}
|
|
} finally {
|
|
setRefreshing(false);
|
|
}
|
|
}
|
|
|
|
if (!metrics || !company) {
|
|
return <div className="text-center py-10">Loading dashboard...</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header with company info */}
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h1 className="text-2xl font-bold">{company.name}</h1>
|
|
<p className="text-gray-600">
|
|
Dashboard updated{" "}
|
|
{new Date(metrics.lastUpdated || Date.now()).toLocaleString()}
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
<button
|
|
className="bg-blue-600 text-white py-2 px-4 rounded-lg shadow-sm hover:bg-blue-700 disabled:opacity-50"
|
|
onClick={handleRefresh}
|
|
disabled={refreshing || isAuditor}
|
|
>
|
|
{refreshing ? "Refreshing..." : "Refresh Data"}
|
|
</button>
|
|
<button
|
|
className="bg-gray-200 py-2 px-4 rounded-lg shadow-sm hover:bg-gray-300"
|
|
onClick={() => signOut()}
|
|
>
|
|
Sign Out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Metrics Cards */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<MetricsCard label="Total Sessions" value={metrics.totalSessions} />
|
|
<MetricsCard
|
|
label="Avg Sessions/Day"
|
|
value={metrics.avgSessionsPerDay?.toFixed(1)}
|
|
/>
|
|
<MetricsCard
|
|
label="Avg Session Time"
|
|
value={
|
|
metrics.avgSessionLength
|
|
? `${metrics.avgSessionLength.toFixed(1)} min`
|
|
: null
|
|
}
|
|
/>
|
|
<MetricsCard
|
|
label="Avg Sentiment"
|
|
value={
|
|
metrics.avgSentiment
|
|
? metrics.avgSentiment.toFixed(2) + "/10"
|
|
: null
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* Charts Row */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="bg-white p-4 rounded-xl shadow">
|
|
<h3 className="font-bold text-lg mb-3">Sessions by Day</h3>
|
|
<SessionsLineChart sessionsPerDay={metrics.days || {}} />
|
|
</div>
|
|
<div className="bg-white p-4 rounded-xl shadow">
|
|
<h3 className="font-bold text-lg mb-3">Categories</h3>
|
|
<CategoriesBarChart categories={metrics.categories || {}} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Admin Controls */}
|
|
{isAdmin && (
|
|
<>
|
|
<DashboardSettings company={company} session={session} />
|
|
<UserManagement session={session} />
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Our exported component
|
|
export default function DashboardPage() {
|
|
// We don't use useSession here to avoid the error outside the provider
|
|
return <DashboardContent />;
|
|
}
|