183 lines
7.2 KiB
TypeScript
183 lines
7.2 KiB
TypeScript
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
|
|
import { json } from "@remix-run/node";
|
|
import { useLoaderData } from "@remix-run/react";
|
|
import { requireAuthLevel } from "~/utils/auth.server";
|
|
import DashboardLayout from "~/components/DashboardLayout";
|
|
import { PrismaClient } from "@prisma/client";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
export const meta: MetaFunction = () => [{ title: "Dashboard - Phosphat Report" }];
|
|
|
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
const user = await requireAuthLevel(request, 1);
|
|
|
|
// Get dashboard statistics
|
|
const [reportCount, equipmentCount, areaCount] = await Promise.all([
|
|
prisma.report.count(),
|
|
prisma.equipment.count(),
|
|
prisma.area.count(),
|
|
]);
|
|
|
|
// Get recent reports
|
|
const recentReports = await prisma.report.findMany({
|
|
take: 5,
|
|
orderBy: { createdDate: 'desc' },
|
|
include: {
|
|
employee: { select: { name: true } },
|
|
area: { select: { name: true } },
|
|
},
|
|
});
|
|
|
|
return json({
|
|
user,
|
|
stats: {
|
|
reportCount,
|
|
equipmentCount,
|
|
areaCount,
|
|
},
|
|
recentReports,
|
|
});
|
|
};
|
|
|
|
export default function Dashboard() {
|
|
const { user, stats, recentReports } = useLoaderData<typeof loader>();
|
|
|
|
return (
|
|
<DashboardLayout user={user}>
|
|
<div className="space-y-6">
|
|
{/* Welcome Section */}
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
<div className="px-4 py-5 sm:p-6">
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
|
Welcome back, {user.name}!
|
|
</h2>
|
|
<p className="text-gray-600">
|
|
Here's what's happening with your phosphat operations today.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-1 gap-5 sm:grid-cols-3">
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
<div className="p-5">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<svg className="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
<div className="ml-5 w-0 flex-1">
|
|
<dl>
|
|
<dt className="text-sm font-medium text-gray-500 truncate">
|
|
Total Reports
|
|
</dt>
|
|
<dd className="text-lg font-medium text-gray-900">
|
|
{stats.reportCount}
|
|
</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
<div className="p-5">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<svg className="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
|
</svg>
|
|
</div>
|
|
<div className="ml-5 w-0 flex-1">
|
|
<dl>
|
|
<dt className="text-sm font-medium text-gray-500 truncate">
|
|
Equipment Units
|
|
</dt>
|
|
<dd className="text-lg font-medium text-gray-900">
|
|
{stats.equipmentCount}
|
|
</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
<div className="p-5">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<svg className="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
</div>
|
|
<div className="ml-5 w-0 flex-1">
|
|
<dl>
|
|
<dt className="text-sm font-medium text-gray-500 truncate">
|
|
Active Areas
|
|
</dt>
|
|
<dd className="text-lg font-medium text-gray-900">
|
|
{stats.areaCount}
|
|
</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Recent Reports */}
|
|
<div className="bg-white shadow overflow-hidden sm:rounded-md">
|
|
<div className="px-4 py-5 sm:px-6">
|
|
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
|
Recent Reports
|
|
</h3>
|
|
<p className="mt-1 max-w-2xl text-sm text-gray-500">
|
|
Latest activity from your team
|
|
</p>
|
|
</div>
|
|
<ul className="divide-y divide-gray-200">
|
|
{recentReports.length > 0 ? (
|
|
recentReports.map((report) => (
|
|
<li key={report.id}>
|
|
<div className="px-4 py-4 sm:px-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<div className="h-8 w-8 rounded-full bg-indigo-500 flex items-center justify-center">
|
|
<span className="text-sm font-medium text-white">
|
|
{report.employee.name.charAt(0)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4">
|
|
<div className="text-sm font-medium text-gray-900">
|
|
{report.employee.name}
|
|
</div>
|
|
<div className="text-sm text-gray-500">
|
|
{report.area.name} - {report.shift} shift
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-sm text-gray-500">
|
|
{new Date(report.createdDate).toLocaleDateString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
))
|
|
) : (
|
|
<li>
|
|
<div className="px-4 py-4 sm:px-6 text-center text-gray-500">
|
|
No reports yet. Create your first report to get started!
|
|
</div>
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</DashboardLayout>
|
|
);
|
|
} |