Edit account book for owner
All checks were successful
Build frontend / build (push) Successful in 1m6s
All checks were successful
Build frontend / build (push) Successful in 1m6s
This commit is contained in:
@ -752,6 +752,12 @@ export default function ClientLayout({ children }) {
|
|||||||
الإشعارات
|
الإشعارات
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link href="/settings" className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors ${isHomePage ? "text-white/80 hover:text-amber-300 hover:bg-white/10" : "text-gray-600 hover:text-amber-600 hover:bg-gray-100"}`}>
|
||||||
|
<Settings className="w-5 h-5" />
|
||||||
|
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
||||||
|
الإعدادات
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
<button onClick={toggleTheme} className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors ${isHomePage ? "text-white/80 hover:text-amber-300 hover:bg-white/10" : "text-gray-600 hover:text-amber-600 hover:bg-gray-100"}`}>
|
<button onClick={toggleTheme} className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors ${isHomePage ? "text-white/80 hover:text-amber-300 hover:bg-white/10" : "text-gray-600 hover:text-amber-600 hover:bg-gray-100"}`}>
|
||||||
{theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
|
{theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
|
||||||
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { Home, Building, Calendar, Settings, CreditCard, Briefcase } from "lucide-react";
|
import { Home, Building, Calendar, CreditCard, Briefcase, BookOpen } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
||||||
@ -20,7 +20,7 @@ export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
|||||||
...(isOwnerOrAgent ? [{ href: "/owner/properties", label: "عقاراتي", icon: Briefcase }] : []),
|
...(isOwnerOrAgent ? [{ href: "/owner/properties", label: "عقاراتي", icon: Briefcase }] : []),
|
||||||
{ href: bookingsHref, label: "الحجوزات", icon: Calendar },
|
{ href: bookingsHref, label: "الحجوزات", icon: Calendar },
|
||||||
{ href: "/payments", label: "المدفوعات", icon: CreditCard },
|
{ href: "/payments", label: "المدفوعات", icon: CreditCard },
|
||||||
{ href: "/settings", label: "الإعدادات", icon: Settings },
|
...(isOwnerOrAgent ? [{ href: "/owner/account-book", label: "دفتر الحسابات", icon: BookOpen }] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
const isActive = (href) => {
|
const isActive = (href) => {
|
||||||
|
|||||||
@ -7,17 +7,16 @@ import {
|
|||||||
DollarSign,
|
DollarSign,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
Calendar,
|
Calendar,
|
||||||
Users,
|
|
||||||
Building,
|
Building,
|
||||||
Download,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
Star,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import AuthService from '@/app/services/AuthService';
|
import AuthService from '@/app/services/AuthService';
|
||||||
import { getOwnerStatistics } from '@/app/utils/api';
|
import { getOwnerStatistics } from '@/app/utils/api';
|
||||||
|
|
||||||
const StatCard = ({ title, value, icon: Icon, color, subtitle }) => (
|
const StatCard = ({ title, value, icon: Icon, color, subtitle, isNA }) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
@ -29,7 +28,7 @@ const StatCard = ({ title, value, icon: Icon, color, subtitle }) => (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm text-gray-500 mb-1">{title}</h3>
|
<h3 className="text-sm text-gray-500 mb-1">{title}</h3>
|
||||||
<div className="text-2xl font-bold text-gray-900">{value}</div>
|
<div className="text-2xl font-bold text-gray-900">{isNA ? 'N/A' : value}</div>
|
||||||
{subtitle && <p className="text-xs text-gray-400 mt-1">{subtitle}</p>}
|
{subtitle && <p className="text-xs text-gray-400 mt-1">{subtitle}</p>}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
@ -39,11 +38,18 @@ export default function OwnerAccountBookPage() {
|
|||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
totalRevenue: 0,
|
totalRevenue: null,
|
||||||
totalReservations: 0,
|
totalReservations: null,
|
||||||
activeProperties: 0,
|
activeProperties: null,
|
||||||
|
financialRevenue: null,
|
||||||
|
financialCommission: null,
|
||||||
|
financialBalance: null,
|
||||||
|
directRevenue: null,
|
||||||
|
directCommission: null,
|
||||||
|
directBalance: null,
|
||||||
|
directRating: null,
|
||||||
|
directPostponements: null,
|
||||||
});
|
});
|
||||||
const [transactions, setTransactions] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (AuthService.isGuest()) {
|
if (AuthService.isGuest()) {
|
||||||
@ -68,13 +74,18 @@ export default function OwnerAccountBookPage() {
|
|||||||
const data = await getOwnerStatistics();
|
const data = await getOwnerStatistics();
|
||||||
if (data) {
|
if (data) {
|
||||||
setStats({
|
setStats({
|
||||||
totalRevenue: data.totalRevenue ?? 0,
|
totalRevenue: data.totalRevenue ?? null,
|
||||||
totalReservations: data.totalReservations ?? 0,
|
totalReservations: data.totalReservations ?? null,
|
||||||
activeProperties: data.activeProperties ?? 0,
|
activeProperties: data.activeProperties ?? null,
|
||||||
|
financialRevenue: data.financialRevenue ?? null,
|
||||||
|
financialCommission: data.financialCommission ?? null,
|
||||||
|
financialBalance: data.financialBalance ?? null,
|
||||||
|
directRevenue: data.directRevenue ?? null,
|
||||||
|
directCommission: data.directCommission ?? null,
|
||||||
|
directBalance: data.directBalance ?? null,
|
||||||
|
directRating: data.directRating ?? null,
|
||||||
|
directPostponements: data.directPostponements ?? null,
|
||||||
});
|
});
|
||||||
if (data.transactions) {
|
|
||||||
setTransactions(data.transactions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('تعذر تحميل إحصائيات الحساب');
|
toast.error('تعذر تحميل إحصائيات الحساب');
|
||||||
@ -86,14 +97,25 @@ export default function OwnerAccountBookPage() {
|
|||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
const num = Number(amount) || 0;
|
const num = Number(amount);
|
||||||
|
if (isNaN(num)) return 'N/A';
|
||||||
return num.toLocaleString() + ' ل.س';
|
return num.toLocaleString() + ' ل.س';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExport = () => {
|
const formatNumber = (val) => {
|
||||||
toast.success('جاري تصدير البيانات...');
|
const num = Number(val);
|
||||||
|
if (isNaN(num)) return 'N/A';
|
||||||
|
return num.toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatRating = (val) => {
|
||||||
|
const num = Number(val);
|
||||||
|
if (isNaN(num)) return 'N/A';
|
||||||
|
return num.toFixed(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNA = (val) => val === null || val === undefined || val === '';
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
@ -122,115 +144,86 @@ export default function OwnerAccountBookPage() {
|
|||||||
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">دفتر الحسابات</h1>
|
<h1 className="text-3xl font-bold text-gray-900">دفتر الحسابات</h1>
|
||||||
<p className="text-gray-600">
|
|
||||||
مرحباً {user?.name}، نظرة عامة على حساباتك
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
onClick={handleExport}
|
|
||||||
className="px-5 py-2.5 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors flex items-center gap-2 shadow-sm"
|
|
||||||
>
|
|
||||||
<Download className="w-5 h-5" />
|
|
||||||
تصدير
|
|
||||||
</button>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
|
||||||
<StatCard
|
|
||||||
title="إجمالي الإيرادات"
|
|
||||||
value={formatCurrency(stats.totalRevenue)}
|
|
||||||
icon={DollarSign}
|
|
||||||
color="bg-green-500"
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
title="إجمالي الحجوزات"
|
|
||||||
value={stats.totalReservations}
|
|
||||||
icon={Calendar}
|
|
||||||
color="bg-blue-500"
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
title="العقارات النشطة"
|
|
||||||
value={stats.activeProperties}
|
|
||||||
icon={Building}
|
|
||||||
color="bg-amber-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.2 }}
|
className="mb-8"
|
||||||
className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden"
|
|
||||||
>
|
>
|
||||||
<div className="px-6 py-4 border-b border-gray-200">
|
<h2 className="text-xl font-bold text-gray-900 mb-4">نظرة مالية للمالك</h2>
|
||||||
<h2 className="text-lg font-bold text-gray-900">المعاملات المالية</h2>
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||||
|
<StatCard
|
||||||
|
title="الإيرادات"
|
||||||
|
value={formatCurrency(stats.financialRevenue)}
|
||||||
|
icon={TrendingUp}
|
||||||
|
color="bg-emerald-500"
|
||||||
|
isNA={isNA(stats.financialRevenue)}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
title="العمولة"
|
||||||
|
value={formatCurrency(stats.financialCommission)}
|
||||||
|
icon={DollarSign}
|
||||||
|
color="bg-orange-500"
|
||||||
|
isNA={isNA(stats.financialCommission)}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
title="الرصيد المتبقي"
|
||||||
|
value={formatCurrency(stats.financialBalance)}
|
||||||
|
icon={DollarSign}
|
||||||
|
color="bg-blue-500"
|
||||||
|
isNA={isNA(stats.financialBalance)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.1 }}
|
||||||
|
className="mb-8"
|
||||||
|
>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-4">إحصائيات العقارات المباشرة</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||||
|
<StatCard
|
||||||
|
title="الإيرادات"
|
||||||
|
value={formatCurrency(stats.directRevenue)}
|
||||||
|
icon={TrendingUp}
|
||||||
|
color="bg-emerald-500"
|
||||||
|
isNA={isNA(stats.directRevenue)}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
title="العمولة"
|
||||||
|
value={formatCurrency(stats.directCommission)}
|
||||||
|
icon={DollarSign}
|
||||||
|
color="bg-orange-500"
|
||||||
|
isNA={isNA(stats.directCommission)}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
title="الرصيد المتبقي"
|
||||||
|
value={formatCurrency(stats.directBalance)}
|
||||||
|
icon={DollarSign}
|
||||||
|
color="bg-blue-500"
|
||||||
|
isNA={isNA(stats.directBalance)}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
title="التقييم العام"
|
||||||
|
value={formatRating(stats.directRating)}
|
||||||
|
icon={Star}
|
||||||
|
color="bg-purple-500"
|
||||||
|
isNA={isNA(stats.directRating)}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
title="مرات التأجيل"
|
||||||
|
value={formatNumber(stats.directPostponements)}
|
||||||
|
icon={Calendar}
|
||||||
|
color="bg-red-500"
|
||||||
|
isNA={isNA(stats.directPostponements)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{transactions.length > 0 ? (
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
|
||||||
<thead className="bg-gray-50">
|
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
||||||
التاريخ
|
|
||||||
</th>
|
|
||||||
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
||||||
البيان
|
|
||||||
</th>
|
|
||||||
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
||||||
المبلغ
|
|
||||||
</th>
|
|
||||||
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
||||||
الحالة
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="bg-white divide-y divide-gray-100">
|
|
||||||
{transactions.map((tx, idx) => (
|
|
||||||
<tr
|
|
||||||
key={tx.id || idx}
|
|
||||||
className={`hover:bg-amber-50/40 transition-colors ${
|
|
||||||
idx % 2 === 0 ? 'bg-white' : 'bg-gray-50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-700">
|
|
||||||
{tx.date}
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-800 font-medium">
|
|
||||||
{tx.description}
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-3 whitespace-nowrap text-center font-mono font-semibold text-gray-800">
|
|
||||||
{formatCurrency(tx.amount)}
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-3 whitespace-nowrap text-center">
|
|
||||||
<span
|
|
||||||
className={`inline-flex items-center px-2 py-1 rounded-lg text-xs font-medium ${
|
|
||||||
tx.status === 'completed'
|
|
||||||
? 'bg-green-100 text-green-800'
|
|
||||||
: tx.status === 'pending'
|
|
||||||
? 'bg-yellow-100 text-yellow-800'
|
|
||||||
: 'bg-red-100 text-red-800'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tx.status === 'completed'
|
|
||||||
? 'مكتمل'
|
|
||||||
: tx.status === 'pending'
|
|
||||||
? 'قيد الانتظار'
|
|
||||||
: 'ملغي'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="p-12 text-center">
|
|
||||||
<DollarSign className="w-12 h-12 text-gray-300 mx-auto mb-4" />
|
|
||||||
<p className="text-gray-500">لا توجد معاملات مالية بعد</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user