car_mms/app/routes/dashboard.tsx
2025-09-11 14:22:27 +03:00

233 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}