Edit account book for owner
All checks were successful
Build frontend / build (push) Successful in 1m6s

This commit is contained in:
Rahaf
2026-06-28 23:22:35 +03:00
parent 253e1b7542
commit dc64178f94
3 changed files with 118 additions and 119 deletions

View File

@ -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">

View File

@ -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) => {

View File

@ -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>