Files
SweetHome/app/reports/page.js
mouazkh 00ccf5f262
All checks were successful
Build frontend / build (push) Successful in 55s
the best in the west is mouaz
2026-05-25 21:27:39 +03:00

372 lines
16 KiB
JavaScript

'use client';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useRouter } from 'next/navigation';
import toast, { Toaster } from 'react-hot-toast';
import {
FileText,
Calendar,
DollarSign,
Send,
Loader2,
CheckCircle,
AlertCircle,
User,
MessageSquare,
Hash,
} from 'lucide-react';
import { submitReport, submitReservationReport, submitSaleReport } from '../utils/api';
import AuthService from '../services/AuthService';
const tabs = [
{ id: 'general', label: 'تقرير عام', icon: FileText },
{ id: 'reservation', label: 'تقرير حجز', icon: Calendar },
{ id: 'sale', label: 'تقرير بيع', icon: DollarSign },
];
export default function ReportsPage() {
const router = useRouter();
const [activeTab, setActiveTab] = useState('general');
const [isLoading, setIsLoading] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
useEffect(() => {
if (!AuthService.isAuthenticated()) {
router.push('/login');
}
}, [router]);
const [generalForm, setGeneralForm] = useState({ subject: '', body: '' });
const [reservationForm, setReservationForm] = useState({ reservationId: '', message: '', reporter: 'customer' });
const [saleForm, setSaleForm] = useState({ saleId: '', message: '', reporter: 'buyer' });
const [errors, setErrors] = useState({});
const validateGeneral = () => {
const e = {};
if (!generalForm.subject.trim()) e.subject = 'الموضوع مطلوب';
if (!generalForm.body.trim()) e.body = 'الوصف مطلوب';
setErrors(e);
return Object.keys(e).length === 0;
};
const validateReservation = () => {
const e = {};
if (!reservationForm.reservationId.trim()) e.reservationId = 'رقم الحجز مطلوب';
if (!reservationForm.message.trim()) e.message = 'الرسالة مطلوبة';
setErrors(e);
return Object.keys(e).length === 0;
};
const validateSale = () => {
const e = {};
if (!saleForm.saleId.trim()) e.saleId = 'رقم البيع مطلوب';
if (!saleForm.message.trim()) e.message = 'الرسالة مطلوبة';
setErrors(e);
return Object.keys(e).length === 0;
};
const handleGeneralSubmit = async (e) => {
e.preventDefault();
if (!validateGeneral()) return;
setIsLoading(true);
try {
await submitReport(generalForm.subject, generalForm.body);
setIsSuccess(true);
toast.success('تم إرسال التقرير بنجاح!', {
style: { background: '#dcfce7', color: '#166534' },
});
setGeneralForm({ subject: '', body: '' });
setTimeout(() => setIsSuccess(false), 2000);
} catch (err) {
toast.error(err.message || 'فشل إرسال التقرير', {
style: { background: '#fee2e2', color: '#991b1b' },
});
} finally {
setIsLoading(false);
}
};
const handleReservationSubmit = async (e) => {
e.preventDefault();
if (!validateReservation()) return;
setIsLoading(true);
try {
await submitReservationReport(reservationForm);
setIsSuccess(true);
toast.success('تم إرسال تقرير الحجز!', {
style: { background: '#dcfce7', color: '#166534' },
});
setReservationForm({ reservationId: '', message: '', reporter: 'customer' });
setTimeout(() => setIsSuccess(false), 2000);
} catch (err) {
toast.error(err.message || 'فشل إرسال التقرير', {
style: { background: '#fee2e2', color: '#991b1b' },
});
} finally {
setIsLoading(false);
}
};
const handleSaleSubmit = async (e) => {
e.preventDefault();
if (!validateSale()) return;
setIsLoading(true);
try {
await submitSaleReport(saleForm);
setIsSuccess(true);
toast.success('تم إرسال تقرير البيع!', {
style: { background: '#dcfce7', color: '#166534' },
});
setSaleForm({ saleId: '', message: '', reporter: 'buyer' });
setTimeout(() => setIsSuccess(false), 2000);
} catch (err) {
toast.error(err.message || 'فشل إرسال التقرير', {
style: { background: '#fee2e2', color: '#991b1b' },
});
} finally {
setIsLoading(false);
}
};
const tabContent = {
general: (
<form onSubmit={handleGeneralSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">الموضوع</label>
<div className="relative group">
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<FileText className={`w-5 h-5 ${errors.subject ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
</div>
<input
type="text"
value={generalForm.subject}
onChange={(e) => { setGeneralForm({ ...generalForm, subject: e.target.value }); if (errors.subject) setErrors({ ...errors, subject: null }); }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.subject ? 'border-red-500' : 'border-gray-300'}`}
placeholder="أدخل موضوع التقرير"
/>
</div>
{errors.subject && <p className="text-red-500 text-sm mt-1">{errors.subject}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">الوصف</label>
<textarea
value={generalForm.body}
onChange={(e) => { setGeneralForm({ ...generalForm, body: e.target.value }); if (errors.body) setErrors({ ...errors, body: null }); }}
rows={5}
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.body ? 'border-red-500' : 'border-gray-300'}`}
placeholder="اشرح التفاصيل..."
/>
{errors.body && <p className="text-red-500 text-sm mt-1">{errors.body}</p>}
</div>
<motion.button
type="submit"
disabled={isLoading}
className="w-full bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 rounded-xl font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{isLoading ? (
<><Loader2 className="w-5 h-5 animate-spin" /> جاري الإرسال...</>
) : isSuccess ? (
<><CheckCircle className="w-5 h-5" /> تم الإرسال!</>
) : (
<><Send className="w-5 h-5" /> إرسال التقرير</>
)}
</motion.button>
</form>
),
reservation: (
<form onSubmit={handleReservationSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">رقم الحجز</label>
<div className="relative group">
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<Hash className={`w-5 h-5 ${errors.reservationId ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
</div>
<input
type="text"
value={reservationForm.reservationId}
onChange={(e) => { setReservationForm({ ...reservationForm, reservationId: e.target.value }); if (errors.reservationId) setErrors({ ...errors, reservationId: null }); }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.reservationId ? 'border-red-500' : 'border-gray-300'}`}
placeholder="أدخل رقم الحجز"
/>
</div>
{errors.reservationId && <p className="text-red-500 text-sm mt-1">{errors.reservationId}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">الرسالة</label>
<textarea
value={reservationForm.message}
onChange={(e) => { setReservationForm({ ...reservationForm, message: e.target.value }); if (errors.message) setErrors({ ...errors, message: null }); }}
rows={4}
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.message ? 'border-red-500' : 'border-gray-300'}`}
placeholder="اشرح المشكلة..."
/>
{errors.message && <p className="text-red-500 text-sm mt-1">{errors.message}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">المبلغ</label>
<div className="flex gap-3">
{['customer', 'owner'].map((val) => (
<button
key={val}
type="button"
onClick={() => setReservationForm({ ...reservationForm, reporter: val })}
className={`flex-1 py-3 rounded-xl border-2 font-medium transition-all flex items-center justify-center gap-2 ${
reservationForm.reporter === val
? 'border-amber-500 bg-amber-50 text-amber-700'
: 'border-gray-200 text-gray-600 hover:border-gray-300'
}`}
>
<User className="w-4 h-4" />
{val === 'customer' ? 'مستأجر' : 'مالك'}
</button>
))}
</div>
</div>
<motion.button
type="submit"
disabled={isLoading}
className="w-full bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 rounded-xl font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{isLoading ? (
<><Loader2 className="w-5 h-5 animate-spin" /> جاري الإرسال...</>
) : isSuccess ? (
<><CheckCircle className="w-5 h-5" /> تم الإرسال!</>
) : (
<><Send className="w-5 h-5" /> إرسال التقرير</>
)}
</motion.button>
</form>
),
sale: (
<form onSubmit={handleSaleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">رقم البيع</label>
<div className="relative group">
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<Hash className={`w-5 h-5 ${errors.saleId ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
</div>
<input
type="text"
value={saleForm.saleId}
onChange={(e) => { setSaleForm({ ...saleForm, saleId: e.target.value }); if (errors.saleId) setErrors({ ...errors, saleId: null }); }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.saleId ? 'border-red-500' : 'border-gray-300'}`}
placeholder="أدخل رقم البيع"
/>
</div>
{errors.saleId && <p className="text-red-500 text-sm mt-1">{errors.saleId}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">الرسالة</label>
<textarea
value={saleForm.message}
onChange={(e) => { setSaleForm({ ...saleForm, message: e.target.value }); if (errors.message) setErrors({ ...errors, message: null }); }}
rows={4}
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.message ? 'border-red-500' : 'border-gray-300'}`}
placeholder="اشرح المشكلة..."
/>
{errors.message && <p className="text-red-500 text-sm mt-1">{errors.message}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">المبلغ</label>
<div className="flex gap-3">
{['buyer', 'seller'].map((val) => (
<button
key={val}
type="button"
onClick={() => setSaleForm({ ...saleForm, reporter: val })}
className={`flex-1 py-3 rounded-xl border-2 font-medium transition-all flex items-center justify-center gap-2 ${
saleForm.reporter === val
? 'border-amber-500 bg-amber-50 text-amber-700'
: 'border-gray-200 text-gray-600 hover:border-gray-300'
}`}
>
<User className="w-4 h-4" />
{val === 'buyer' ? 'مشتري' : 'بائع'}
</button>
))}
</div>
</div>
<motion.button
type="submit"
disabled={isLoading}
className="w-full bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 rounded-xl font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{isLoading ? (
<><Loader2 className="w-5 h-5 animate-spin" /> جاري الإرسال...</>
) : isSuccess ? (
<><CheckCircle className="w-5 h-5" /> تم الإرسال!</>
) : (
<><Send className="w-5 h-5" /> إرسال التقرير</>
)}
</motion.button>
</form>
),
};
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-3xl">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-8"
>
<h1 className="text-3xl font-bold text-gray-900 mb-2">الإبلاغ عن مشكلة</h1>
<p className="text-gray-600">أخبرنا عن المشكلة التي تواجهها وسنقوم بمعالجتها</p>
</motion.div>
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div className="flex border-b border-gray-200">
{tabs.map((tab) => {
const Icon = tab.icon;
return (
<button
key={tab.id}
onClick={() => { setActiveTab(tab.id); setIsSuccess(false); setErrors({}); }}
className={`flex-1 flex items-center justify-center gap-2 py-4 text-sm font-medium transition-all relative ${
activeTab === tab.id
? 'text-amber-600'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<Icon className="w-4 h-4" />
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="activeTab"
className="absolute bottom-0 left-0 right-0 h-0.5 bg-amber-500"
/>
)}
</button>
);
})}
</div>
<div className="p-6">
<AnimatePresence mode="wait">
<motion.div
key={activeTab}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.2 }}
>
{tabContent[activeTab]}
</motion.div>
</AnimatePresence>
</div>
</div>
</div>
</div>
);
}