233 lines
12 KiB
TypeScript
233 lines
12 KiB
TypeScript
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
|
||
import { json } from "@remix-run/node";
|
||
import { useLoaderData } from "@remix-run/react";
|
||
import { requireAuthentication } from "~/lib/auth-middleware.server";
|
||
import { getFinancialSummary } from "~/lib/financial-reporting.server";
|
||
import { prisma } from "~/lib/db.server";
|
||
import { DashboardLayout } from "~/components/layout/DashboardLayout";
|
||
import { useSettings } from "~/contexts/SettingsContext";
|
||
|
||
export const meta: MetaFunction = () => {
|
||
return [
|
||
{ title: "لوحة التحكم - نظام إدارة صيانة السيارات" },
|
||
{ name: "description", content: "لوحة التحكم الرئيسية لنظام إدارة صيانة السيارات" },
|
||
];
|
||
};
|
||
|
||
export async function loader({ request }: LoaderFunctionArgs) {
|
||
const user = await requireAuthentication(request);
|
||
|
||
// Get dashboard statistics
|
||
const [
|
||
customersCount,
|
||
vehiclesCount,
|
||
maintenanceVisitsCount,
|
||
financialSummary
|
||
] = await Promise.all([
|
||
prisma.customer.count(),
|
||
prisma.vehicle.count(),
|
||
prisma.maintenanceVisit.count(),
|
||
user.authLevel <= 2 ? getFinancialSummary() : null, // Only for admin and above
|
||
]);
|
||
|
||
return json({
|
||
user,
|
||
stats: {
|
||
customersCount,
|
||
vehiclesCount,
|
||
maintenanceVisitsCount,
|
||
financialSummary,
|
||
}
|
||
});
|
||
}
|
||
|
||
export default function Dashboard() {
|
||
const { formatCurrency } = useSettings();
|
||
const { user, stats } = useLoaderData<typeof loader>();
|
||
|
||
return (
|
||
<DashboardLayout user={user}>
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900">لوحة التحكم</h1>
|
||
<p className="text-gray-600">مرحباً بك، {user.name}</p>
|
||
</div>
|
||
|
||
{/* Statistics Cards */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
{/* Customers Card */}
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<div className="flex items-center">
|
||
<div className="flex-1">
|
||
<p className="text-sm font-medium text-gray-600">العملاء</p>
|
||
<p className="text-2xl font-bold text-blue-600">
|
||
{stats.customersCount}
|
||
</p>
|
||
<p className="text-sm text-gray-500">إجمالي العملاء المسجلين</p>
|
||
</div>
|
||
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Vehicles Card */}
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<div className="flex items-center">
|
||
<div className="flex-1">
|
||
<p className="text-sm font-medium text-gray-600">المركبات</p>
|
||
<p className="text-2xl font-bold text-green-600">
|
||
{stats.vehiclesCount}
|
||
</p>
|
||
<p className="text-sm text-gray-500">إجمالي المركبات المسجلة</p>
|
||
</div>
|
||
<div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
||
<svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 17a2 2 0 11-4 0 2 2 0 014 0zM19 17a2 2 0 11-4 0 2 2 0 014 0z" />
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16V6a1 1 0 00-1-1H4a1 1 0 00-1 1v10a1 1 0 001 1h1m8-1a1 1 0 01-1-1V8a1 1 0 011-1h2.586a1 1 0 01.707.293l3.414 3.414a1 1 0 01.293.707V16a1 1 0 01-1 1h-1m-6 0a1 1 0 001 1h4a1 1 0 001-1m-6 0V9a1 1 0 00-1-1v0a1 1 0 00-1 1v8a1 1 0 001 1z" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Maintenance Visits Card */}
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<div className="flex items-center">
|
||
<div className="flex-1">
|
||
<p className="text-sm font-medium text-gray-600">زيارات الصيانة</p>
|
||
<p className="text-2xl font-bold text-purple-600">
|
||
{stats.maintenanceVisitsCount}
|
||
</p>
|
||
<p className="text-sm text-gray-500">إجمالي زيارات الصيانة</p>
|
||
</div>
|
||
<div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
|
||
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Financial Summary Card (Admin only) */}
|
||
{stats.financialSummary && (
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<div className="flex items-center">
|
||
<div className="flex-1">
|
||
<p className="text-sm font-medium text-gray-600">صافي الربح</p>
|
||
<p className={`text-2xl font-bold ${stats.financialSummary.netProfit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||
{formatCurrency(stats.financialSummary.netProfit)}
|
||
</p>
|
||
<p className="text-sm text-gray-500">
|
||
هامش الربح: {stats.financialSummary.profitMargin.toFixed(1)}%
|
||
</p>
|
||
</div>
|
||
<div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
|
||
stats.financialSummary.netProfit >= 0 ? 'bg-green-100' : 'bg-red-100'
|
||
}`}>
|
||
<svg className={`w-6 h-6 ${stats.financialSummary.netProfit >= 0 ? 'text-green-600' : 'text-red-600'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Financial Summary Details (Admin only) */}
|
||
{stats.financialSummary && (
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<div className="flex justify-between items-center mb-4">
|
||
<h2 className="text-lg font-semibold text-gray-900">الملخص المالي</h2>
|
||
<a
|
||
href="/financial-reports"
|
||
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
|
||
>
|
||
عرض التقارير المفصلة ←
|
||
</a>
|
||
</div>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<div className="text-center">
|
||
<p className="text-sm text-gray-600">إجمالي الإيرادات</p>
|
||
<p className="text-xl font-bold text-green-600">
|
||
{formatCurrency(stats.financialSummary.totalIncome)}
|
||
</p>
|
||
<p className="text-xs text-gray-500">
|
||
{stats.financialSummary.incomeCount} عملية
|
||
</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<p className="text-sm text-gray-600">إجمالي المصروفات</p>
|
||
<p className="text-xl font-bold text-red-600">
|
||
{formatCurrency(stats.financialSummary.totalExpenses)}
|
||
</p>
|
||
<p className="text-xs text-gray-500">
|
||
{stats.financialSummary.expenseCount} مصروف
|
||
</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<p className="text-sm text-gray-600">صافي الربح</p>
|
||
<p className={`text-xl font-bold ${stats.financialSummary.netProfit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||
{formatCurrency(stats.financialSummary.netProfit)}
|
||
</p>
|
||
<p className="text-xs text-gray-500">
|
||
{stats.financialSummary.profitMargin.toFixed(1)}% هامش ربح
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Quick Actions */}
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<h2 className="text-lg font-semibold text-gray-900 mb-4">الإجراءات السريعة</h2>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
<a
|
||
href="/customers"
|
||
className="flex items-center p-3 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors"
|
||
>
|
||
<svg className="w-5 h-5 text-blue-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||
</svg>
|
||
<span className="text-sm font-medium text-blue-900">إضافة عميل جديد</span>
|
||
</a>
|
||
|
||
<a
|
||
href="/vehicles"
|
||
className="flex items-center p-3 bg-green-50 rounded-lg hover:bg-green-100 transition-colors"
|
||
>
|
||
<svg className="w-5 h-5 text-green-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||
</svg>
|
||
<span className="text-sm font-medium text-green-900">تسجيل مركبة جديدة</span>
|
||
</a>
|
||
|
||
<a
|
||
href="/maintenance-visits"
|
||
className="flex items-center p-3 bg-purple-50 rounded-lg hover:bg-purple-100 transition-colors"
|
||
>
|
||
<svg className="w-5 h-5 text-purple-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||
</svg>
|
||
<span className="text-sm font-medium text-purple-900">إضافة زيارة صيانة</span>
|
||
</a>
|
||
|
||
{user.authLevel <= 2 && (
|
||
<a
|
||
href="/expenses"
|
||
className="flex items-center p-3 bg-orange-50 rounded-lg hover:bg-orange-100 transition-colors"
|
||
>
|
||
<svg className="w-5 h-5 text-orange-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||
</svg>
|
||
<span className="text-sm font-medium text-orange-900">إضافة مصروف</span>
|
||
</a>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</DashboardLayout>
|
||
);
|
||
} |