This commit is contained in:
2026-03-08 14:27:16 +03:00
parent 66c151653e
commit 11b58b68c3
22 changed files with 4652 additions and 204 deletions

View File

@@ -443,26 +443,137 @@ export default function ExpensesPage() {
</div>
{/* Action Messages */}
{actionData?.success && actionData.message && (
{actionData?.success && 'message' in actionData && actionData.message && (
<div className="bg-green-50 border border-green-200 text-green-800 px-4 py-3 rounded-lg">
{actionData.message}
</div>
)}
{actionData?.error && (
{actionData && !actionData.success && 'error' in actionData && actionData.error && (
<div className="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg">
{actionData.error}
</div>
)}
{/* Expenses Table */}
<DataTable
data={expenses}
columns={columns}
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
<div className="bg-white rounded-lg shadow-sm border">
{expenses.length === 0 ? (
<div className="text-center py-12">
<div className="text-gray-400 text-lg mb-2">💰</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
لا توجد مصروفات
</h3>
<p className="text-gray-500">
لم يتم العثور على أي مصروفات.
</p>
</div>
) : (
<>
{/* Desktop Table View */}
<div className="hidden md:block">
<DataTable
data={expenses as any}
columns={columns}
emptyMessage="لم يتم العثور على أي مصروفات"
/>
</div>
{/* Mobile Card View */}
<div className="md:hidden divide-y divide-gray-200">
{expenses.map((expense: any) => {
const categoryLabel = EXPENSE_CATEGORIES.find(c => c.value === expense.category)?.label;
return (
<div key={expense.id} className="p-4 hover:bg-gray-50">
<div className="space-y-3">
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="font-medium text-gray-900">{expense.description}</div>
<div className="text-sm text-gray-500 mt-1">{categoryLabel || expense.category}</div>
</div>
<div className="text-lg font-semibold text-gray-900">
{formatCurrency(expense.amount)}
</div>
</div>
<div className="grid grid-cols-2 gap-2 text-sm">
<div>
<span className="text-gray-600">تاريخ المصروف: </span>
<span className="text-gray-900">{formatDate(expense.expenseDate)}</span>
</div>
<div>
<span className="text-gray-600">تاريخ الإضافة: </span>
<span className="text-gray-900">{formatDate(expense.createdDate)}</span>
</div>
</div>
<div className="pt-2">
<Button
variant="outline"
size="sm"
onClick={() => handleEditExpense(expense)}
disabled={isLoading}
className="w-full"
>
تعديل
</Button>
</div>
</div>
</div>
);
})}
</div>
</>
)}
{/* Pagination */}
{totalPages > 1 && (
<div className="px-4 py-3 border-t border-gray-200 bg-gray-50">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2 space-x-reverse">
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1 || isLoading}
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
السابق
</button>
<div className="flex items-center space-x-1 space-x-reverse">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
const page = i + 1;
return (
<button
key={page}
onClick={() => handlePageChange(page)}
disabled={isLoading}
className={`px-3 py-2 text-sm font-medium rounded-md ${
currentPage === page
? 'bg-blue-600 text-white'
: 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
{page}
</button>
);
})}
</div>
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages || isLoading}
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
التالي
</button>
</div>
<p className="text-sm text-gray-500">
صفحة {currentPage} من {totalPages}
</p>
</div>
</div>
)}
</div>
{/* Create Expense Modal */}
<Modal
@@ -472,7 +583,7 @@ export default function ExpensesPage() {
>
<ExpenseForm
onCancel={() => setShowCreateModal(false)}
errors={actionData?.action === "create" ? actionData.errors : undefined}
errors={actionData && 'errors' in actionData ? actionData.errors : undefined}
isLoading={isLoading}
/>
</Modal>
@@ -487,7 +598,7 @@ export default function ExpensesPage() {
<ExpenseForm
expense={selectedExpense}
onCancel={() => setShowEditModal(false)}
errors={actionData?.action === "update" ? actionData.errors : undefined}
errors={actionData && 'errors' in actionData ? actionData.errors : undefined}
isLoading={isLoading}
/>
)}

View File

@@ -258,17 +258,17 @@ export default function FinancialReportsPage() {
{/* Charts and Breakdowns */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Income by Maintenance Type */}
<div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-lg font-semibold text-gray-900 mb-4">الإيرادات حسب نوع الصيانة</h2>
<div className="bg-white p-4 sm:p-6 rounded-lg shadow">
<h2 className="text-base sm:text-lg font-semibold text-gray-900 mb-4">الإيرادات حسب نوع الصيانة</h2>
<div className="space-y-3">
{incomeByType.map((item, index) => (
<div key={index} className="flex items-center justify-between">
<div className="flex-1">
<div className="flex justify-between items-center mb-1">
<span className="text-sm font-medium text-gray-700">
<span className="text-xs sm:text-sm font-medium text-gray-700">
{item.category}
</span>
<span className="text-sm text-gray-500">
<span className="text-xs sm:text-sm text-gray-500">
{item.percentage.toFixed(1)}%
</span>
</div>
@@ -279,7 +279,7 @@ export default function FinancialReportsPage() {
></div>
</div>
<div className="flex justify-between items-center mt-1">
<span className="text-sm font-semibold text-gray-900">
<span className="text-xs sm:text-sm font-semibold text-gray-900">
{formatCurrency(item.amount)}
</span>
<span className="text-xs text-gray-500">
@@ -293,17 +293,17 @@ export default function FinancialReportsPage() {
</div>
{/* Expense Breakdown */}
<div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-lg font-semibold text-gray-900 mb-4">تفصيل المصروفات</h2>
<div className="bg-white p-4 sm:p-6 rounded-lg shadow">
<h2 className="text-base sm:text-lg font-semibold text-gray-900 mb-4">تفصيل المصروفات</h2>
<div className="space-y-3">
{expenseBreakdown.map((item, index) => (
<div key={index} className="flex items-center justify-between">
<div className="flex-1">
<div className="flex justify-between items-center mb-1">
<span className="text-sm font-medium text-gray-700">
<span className="text-xs sm:text-sm font-medium text-gray-700">
{item.category}
</span>
<span className="text-sm text-gray-500">
<span className="text-xs sm:text-sm text-gray-500">
{item.percentage.toFixed(1)}%
</span>
</div>
@@ -314,7 +314,7 @@ export default function FinancialReportsPage() {
></div>
</div>
<div className="flex justify-between items-center mt-1">
<span className="text-sm font-semibold text-gray-900">
<span className="text-xs sm:text-sm font-semibold text-gray-900">
{formatCurrency(item.amount)}
</span>
<span className="text-xs text-gray-500">
@@ -331,24 +331,24 @@ export default function FinancialReportsPage() {
{/* Top Customers and Monthly Data */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Top Customers */}
<div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-lg font-semibold text-gray-900 mb-4">أفضل العملاء</h2>
<div className="bg-white p-4 sm:p-6 rounded-lg shadow">
<h2 className="text-base sm:text-lg font-semibold text-gray-900 mb-4">أفضل العملاء</h2>
<div className="space-y-3">
{topCustomers.map((customer, index) => (
<div key={customer.customerId} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-3 space-x-reverse">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0">
<span className="text-sm font-semibold text-blue-600">
{index + 1}
</span>
</div>
<div>
<p className="font-medium text-gray-900">{customer.customerName}</p>
<p className="text-sm text-gray-500">{customer.visitCount} زيارة</p>
<div className="min-w-0 flex-1">
<p className="font-medium text-gray-900 truncate">{customer.customerName}</p>
<p className="text-xs sm:text-sm text-gray-500">{customer.visitCount} زيارة</p>
</div>
</div>
<div className="text-left">
<p className="font-semibold text-gray-900">
<div className="text-left flex-shrink-0 mr-2">
<p className="text-sm sm:text-base font-semibold text-gray-900">
{formatCurrency(customer.totalRevenue)}
</p>
</div>
@@ -358,20 +358,20 @@ export default function FinancialReportsPage() {
</div>
{/* Monthly Performance */}
<div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-lg font-semibold text-gray-900 mb-4">الأداء الشهري</h2>
<div className="bg-white p-4 sm:p-6 rounded-lg shadow">
<h2 className="text-base sm:text-lg font-semibold text-gray-900 mb-4">الأداء الشهري</h2>
<div className="space-y-3 max-h-96 overflow-y-auto">
{monthlyData.slice(-6).reverse().map((month, index) => (
<div key={`${month.year}-${month.month}`} className="p-3 bg-gray-50 rounded-lg">
<div className="flex justify-between items-center mb-2">
<span className="font-medium text-gray-900">
<span className="text-sm sm:text-base font-medium text-gray-900">
{getArabicMonthName(month.month)} {month.year}
</span>
<span className={`font-semibold ${month.profit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
<span className={`text-sm sm:text-base font-semibold ${month.profit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{formatCurrency(month.profit)}
</span>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div className="grid grid-cols-2 gap-2 sm:gap-4 text-xs sm:text-sm">
<div>
<span className="text-gray-600">الإيرادات: </span>
<span className="font-medium text-green-600">