demo2
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user