372 lines
16 KiB
JavaScript
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>
|
|
);
|
|
}
|