Files
SweetHome/app/owner/profits/page.js
2026-04-22 10:52:08 +03:00

592 lines
22 KiB
JavaScript
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.

// 'use client';
// import { useState, useEffect } from 'react';
// import { motion } from 'framer-motion';
// import { useRouter } from 'next/navigation';
// import {
// DollarSign,
// TrendingUp,
// Wallet,
// Star,
// Eye,
// Download,
// CalendarDays
// } from 'lucide-react';
// import toast, { Toaster } from 'react-hot-toast';
// import AuthService from '@/app/services/AuthService';
// const StatCard = ({ title, value, icon: Icon, color }) => {
// return (
// <motion.div
// initial={{ opacity: 0, y: 20 }}
// animate={{ opacity: 1, y: 0 }}
// className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-all"
// >
// <div className="flex items-center justify-between mb-4">
// <div className={`w-12 h-12 ${color} rounded-xl flex items-center justify-center`}>
// <Icon className="w-6 h-6 text-white" />
// </div>
// </div>
// <h3 className="text-sm text-gray-500 mb-1">{title}</h3>
// <div className="text-2xl font-bold text-gray-900">{value}</div>
// </motion.div>
// );
// };
// const PropertyProfitCard = ({ property, onViewDetails }) => {
// const formatCurrency = (amount) => `$${amount?.toLocaleString()}`;
// return (
// <motion.div
// initial={{ opacity: 0, y: 20 }}
// animate={{ opacity: 1, y: 0 }}
// className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden hover:shadow-md transition-all"
// >
// <div className="p-5">
// <div className="flex justify-between items-start mb-4">
// <div>
// <h3 className="font-bold text-lg text-gray-900">{property.title}</h3>
// {property.isNotSeized && (
// <span className="inline-block mt-1 px-2 py-0.5 bg-amber-100 text-amber-800 rounded-full text-xs font-medium">
// غير محجوز
// </span>
// )}
// </div>
// <span className="text-xs text-gray-500">{property.location}</span>
// </div>
// <div className="grid grid-cols-3 gap-4 mb-4">
// <div className="text-center">
// <div className="text-sm text-gray-500">الإيرادات</div>
// <div className="text-lg font-bold text-amber-600">{formatCurrency(property.revenue)}</div>
// </div>
// <div className="text-center">
// <div className="text-sm text-gray-500">العمولة</div>
// <div className="text-lg font-bold text-blue-600">{formatCurrency(property.commission)}</div>
// </div>
// <div className="text-center">
// <div className="text-sm text-gray-500">المتبقي</div>
// <div className="text-lg font-bold text-green-600">{formatCurrency(property.remaining)}</div>
// </div>
// </div>
// <div className="flex justify-between items-center pt-3 border-t border-gray-100">
// <div className="flex items-center gap-2">
// <Star className="w-4 h-4 text-amber-500" />
// <span className="text-sm font-medium text-gray-700">التقييم العام:</span>
// <span className="text-sm font-medium text-gray-900">{property.valuation}</span>
// </div>
// <div className="flex items-center gap-1 text-sm text-gray-500">
// <CalendarDays className="w-4 h-4" />
// <span>مؤجر {property.rentedCount} مرة</span>
// </div>
// </div>
// <button
// onClick={() => onViewDetails(property)}
// className="w-full mt-4 py-2 bg-gray-100 text-gray-700 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
// >
// <Eye className="w-4 h-4" />
// عرض التفاصيل
// </button>
// </div>
// </motion.div>
// );
// };
// const PropertyCalendar = ({ year, month }) => {
// const [currentMonth, setCurrentMonth] = useState(new Date(year, month - 1));
// const monthNames = ['يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'];
// const weekDays = ['إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت', 'أحد'];
// const getDaysInMonth = (date) => {
// return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
// };
// const getFirstDayOfMonth = (date) => {
// const day = new Date(date.getFullYear(), date.getMonth(), 1).getDay();
// return day === 0 ? 6 : day - 1;
// };
// const daysInMonth = getDaysInMonth(currentMonth);
// const firstDayIndex = getFirstDayOfMonth(currentMonth);
// const cells = [];
// for (let i = 0; i < firstDayIndex; i++) {
// cells.push(<div key={`empty-${i}`} className="p-2 md:p-3 text-center" />);
// }
// for (let d = 1; d <= daysInMonth; d++) {
// cells.push(
// <div
// key={d}
// className="p-2 md:p-3 text-center rounded-xl hover:bg-gray-100 transition-colors"
// >
// {d}
// </div>
// );
// }
// return (
// <div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
// <div className="flex justify-between items-center mb-6">
// <h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
// <CalendarDays className="w-5 h-5 text-amber-500" />
// {monthNames[currentMonth.getMonth()]} {currentMonth.getFullYear()}
// </h3>
// <div className="flex gap-2">
// <button
// onClick={() => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1))}
// className="p-2 hover:bg-gray-100 rounded-lg"
// >
// &larr;
// </button>
// <button
// onClick={() => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1))}
// className="p-2 hover:bg-gray-100 rounded-lg"
// >
// &rarr;
// </button>
// </div>
// </div>
// <div className="grid grid-cols-7 gap-1 mb-3 text-center text-sm font-medium text-gray-500">
// {weekDays.map(day => (
// <div key={day}>{day}</div>
// ))}
// </div>
// <div className="grid grid-cols-7 gap-1">
// {cells}
// </div>
// </div>
// );
// };
// export default function OwnerProfitsPage() {
// const router = useRouter();
// const [user, setUser] = useState(null);
// const [isLoading, setIsLoading] = useState(true);
// const [summary] = useState({
// totalRevenue: 4290,
// totalCommission: 644,
// remainingBalance: 3647,
// });
// const [properties] = useState([
// {
// id: 1,
// title: 'Damascus Olive Residence',
// location: 'دمشق، المزة',
// isNotSeized: true,
// revenue: 3240,
// commission: 486,
// remaining: 2754,
// valuation: 'جيد جدا',
// rentedCount: 18,
// },
// ]);
// useEffect(() => {
// if (AuthService.isGuest()) {
// router.push('/auth/choose-role');
// return;
// }
// if (!AuthService.isOwner()) {
// router.push('/');
// return;
// }
// const authUser = AuthService.getUser();
// if (authUser) {
// setUser({
// name: authUser.name || authUser.email,
// email: authUser.email,
// });
// }
// setIsLoading(false);
// }, [router]);
// const formatCurrency = (amount) => `$${amount?.toLocaleString()}`;
// const handleViewDetails = (property) => {
// toast.info(`عرض تفاصيل ${property.title}`);
// };
// const handleExportReport = () => {
// toast.success('جاري تصدير التقرير...');
// };
// if (isLoading) {
// return (
// <div className="min-h-screen flex items-center justify-center">
// <div className="text-center">
// <div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
// <p className="text-gray-600">جاري التحميل...</p>
// </div>
// </div>
// );
// }
// return (
// <div className="min-h-screen bg-gray-50 py-8">
// <Toaster position="top-center" reverseOrder={false} />
// <div className="container mx-auto px-4 max-w-6xl">
// <div className="mb-8">
// <h1 className="text-3xl font-bold text-gray-900 mb-2">دفتر الحسابات</h1>
// <p className="text-gray-600">نظرة عامة على أرباح المالك</p>
// </div>
// <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
// <StatCard
// title="الإيرادات"
// value={formatCurrency(summary.totalRevenue)}
// icon={DollarSign}
// color="bg-green-500"
// />
// <StatCard
// title="العمولة"
// value={formatCurrency(summary.totalCommission)}
// icon={TrendingUp}
// color="bg-blue-500"
// />
// <StatCard
// title="المتبقي"
// value={formatCurrency(summary.remainingBalance)}
// icon={Wallet}
// color="bg-amber-500"
// />
// </div>
// <div className="mb-12">
// <h2 className="text-xl font-bold text-gray-900 mb-4">عقاراتي</h2>
// <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
// {properties.map((property) => (
// <PropertyProfitCard
// key={property.id}
// property={property}
// onViewDetails={handleViewDetails}
// />
// ))}
// </div>
// </div>
// <div className="mb-12">
// <h2 className="text-xl font-bold text-gray-900 mb-4">تقويم العقار</h2>
// <PropertyCalendar year={2026} month={3} />
// </div>
// {/* <div className="flex justify-end">
// <button
// onClick={handleExportReport}
// className="px-6 py-3 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors flex items-center justify-center gap-2"
// >
// <Download className="w-5 h-5" />
// تصدير التقرير
// </button>
// </div> */}
// </div>
// </div>
// );
// }
'use client';
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { useRouter } from 'next/navigation';
import { Download, Loader2 } from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast';
import * as XLSX from 'xlsx';
import AuthService from '@/app/services/AuthService';
export default function OwnerProfitsPage() {
const router = useRouter();
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [tableData, setTableData] = useState([]);
const sampleData = [
{
id: 1,
property: 'A000000001',
bookingNumber: 'XX-101',
fromDate: '2025-05-01',
toDate: '2025-05-07',
amountReceived: 500,
platformCommission: 0,
transferredToOwner: 0,
transferReceipt: '—',
},
{
id: 2,
property: 'A000000002',
bookingNumber: 'XX-202',
fromDate: '2025-05-10',
toDate: '2025-05-15',
amountReceived: 300,
platformCommission: 0,
transferredToOwner: 0,
transferReceipt: '—',
},
{
id: 3,
property: 'A000000003',
bookingNumber: 'XX-309',
fromDate: '2025-06-01',
toDate: '2025-06-05',
amountReceived: 800,
platformCommission: 150,
transferredToOwner: 0,
transferReceipt: 'قيد الانتظار',
},
];
const computeRows = (data) => {
return data.map((item) => {
const platformProfit = item.amountReceived * 0.05;
const ownerDue = item.amountReceived - platformProfit;
return {
...item,
platformProfit,
ownerDue,
};
});
};
useEffect(() => {
if (AuthService.isGuest()) {
router.push('/auth/choose-role');
return;
}
if (!AuthService.isOwner()) {
router.push('/');
return;
}
const authUser = AuthService.getUser();
if (authUser) {
setUser({
name: authUser.name || authUser.email,
email: authUser.email,
});
}
const stored = localStorage.getItem('ownerProfitsTable');
if (stored) {
setTableData(computeRows(JSON.parse(stored)));
} else {
setTableData(computeRows(sampleData));
localStorage.setItem('ownerProfitsTable', JSON.stringify(sampleData));
}
setIsLoading(false);
}, [router]);
const totals = tableData.reduce(
(acc, row) => {
acc.totalAmountReceived += row.amountReceived;
acc.totalCommission += row.platformCommission;
acc.totalPlatformProfit += row.platformProfit;
acc.totalOwnerDue += row.ownerDue;
acc.totalTransferred += row.transferredToOwner;
return acc;
},
{
totalAmountReceived: 0,
totalCommission: 0,
totalPlatformProfit: 0,
totalOwnerDue: 0,
totalTransferred: 0,
}
);
const handleExportReport = () => {
try {
const exportData = tableData.map((row) => ({
'العقار': row.property,
'رقم الحجز': row.bookingNumber,
'من تاريخ': row.fromDate,
'حتى تاريخ': row.toDate,
'العروض المستلم': row.amountReceived,
'عمولة المنصة': row.platformCommission,
'ربح المنصة (5%)': row.platformProfit,
'المستحق للمالك': row.ownerDue,
'تم التحويل للمالك': row.transferredToOwner,
'رقم وصل التحويل': row.transferReceipt,
}));
exportData.push({
'العقار': 'الإجمالي العام',
'رقم الحجز': '',
'من تاريخ': '',
'حتى تاريخ': '',
'العروض المستلم': totals.totalAmountReceived,
'عمولة المنصة': totals.totalCommission,
'ربح المنصة (5%)': totals.totalPlatformProfit,
'المستحق للمالك': totals.totalOwnerDue,
'تم التحويل للمالك': totals.totalTransferred,
'رقم وصل التحويل': '—',
});
const worksheet = XLSX.utils.json_to_sheet(exportData);
const colWidths = [
{ wch: 15 },
{ wch: 12 },
{ wch: 12 },
{ wch: 12 },
{ wch: 14 },
{ wch: 14 },
{ wch: 16 },
{ wch: 16 },
{ wch: 16 },
{ wch: 18 },
];
worksheet['!cols'] = colWidths;
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'أرباح المالك');
XLSX.writeFile(workbook, `تقرير_الأرباح_${new Date().toISOString().slice(0,19).replace(/:/g, '-')}.xlsx`);
toast.success('تم تصدير التقرير بنجاح!');
} catch (error) {
console.error('خطأ في التصدير:', error);
toast.error('حدث خطأ أثناء تصدير التقرير');
}
};
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
<p className="text-gray-600">جاري تحميل بيانات الأرباح...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
<Toaster position="top-center" reverseOrder={false} />
<div className="container mx-auto px-4 max-w-7xl">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
>
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">أرباح المالك</h1>
<p className="text-gray-600">
مرحباً {user?.name}
</p>
</div>
<button
onClick={handleExportReport}
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
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden"
>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-800 text-gray-100">
<tr>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">العقار</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">رقم الحجز</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">من تاريخ</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">حتى تاريخ</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">العروض المستلم</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">عمولة المنصة</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider bg-amber-50 text-amber-800">
ربح المنصة <span className="font-normal text-[11px] block">(5% من العربون)</span>
</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">المستحق للمالك</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">تم التحويل للمالك</th>
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">رقم وصل التحويل</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-100">
{tableData.map((row, idx) => (
<tr
key={row.id}
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 font-medium text-gray-800">
{row.property}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-700">
{row.bookingNumber}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-700">
{row.fromDate}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-700">
{row.toDate}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center font-mono font-semibold text-gray-800">
{row.amountReceived}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center font-mono text-gray-700">
{row.platformCommission}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center font-mono font-bold text-amber-700 bg-amber-50/50">
{row.platformProfit}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center font-mono font-semibold text-emerald-700">
{row.ownerDue}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center font-mono text-gray-700">
{row.transferredToOwner}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-500 text-xs">
{row.transferReceipt}
</td>
</tr>
))}
</tbody>
<tfoot className="bg-gray-100 border-t-2 border-gray-300">
<tr>
<td colSpan="4" className="px-4 py-4 text-right font-bold text-gray-800">
الإجمالي العام
</td>
<td className="px-4 py-4 text-center font-bold font-mono text-gray-800">
{totals.totalAmountReceived}
</td>
<td className="px-4 py-4 text-center font-bold font-mono text-gray-800">
{totals.totalCommission}
</td>
<td className="px-4 py-4 text-center font-bold font-mono text-amber-700 bg-amber-100/60">
{totals.totalPlatformProfit}
</td>
<td className="px-4 py-4 text-center font-bold font-mono text-emerald-700">
{totals.totalOwnerDue}
</td>
<td className="px-4 py-4 text-center font-bold font-mono text-gray-800">
{totals.totalTransferred}
</td>
<td className="px-4 py-4 text-center text-gray-500"></td>
</tr>
</tfoot>
</table>
</div>
<div className="bg-gray-50 px-6 py-3 text-xs text-gray-500 border-t border-gray-200">
<span className="inline-flex items-center gap-1"></span> ملاحظة:
<strong> ربح المنصة </strong> يُحتسب تلقائياً بنسبة <strong className="text-amber-600">5%</strong> من قيمة «العروض المستلم».
</div>
</motion.div>
</div>
</div>
);
}