309 lines
10 KiB
TypeScript
309 lines
10 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useEffect } from 'react'
|
||
import { portalAPI } from '@/lib/api/portal'
|
||
import Modal from '@/components/Modal'
|
||
import LoadingSpinner from '@/components/LoadingSpinner'
|
||
import { toast } from 'react-hot-toast'
|
||
import { Plus } from 'lucide-react'
|
||
|
||
const TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
||
const hour = Math.floor(i / 2).toString().padStart(2, '0')
|
||
const minute = i % 2 === 0 ? '00' : '30'
|
||
return `${hour}:${minute}`
|
||
})
|
||
|
||
const LEAVE_TYPES = [
|
||
{ value: 'ANNUAL', label: 'إجازة سنوية' },
|
||
{ value: 'HOURLY', label: 'إجازة ساعية' },
|
||
]
|
||
|
||
const STATUS_MAP: Record<string, { label: string; color: string }> = {
|
||
PENDING: { label: 'قيد المراجعة', color: 'bg-amber-100 text-amber-800' },
|
||
APPROVED: { label: 'معتمدة', color: 'bg-green-100 text-green-800' },
|
||
REJECTED: { label: 'مرفوضة', color: 'bg-red-100 text-red-800' },
|
||
}
|
||
|
||
export default function PortalLeavePage() {
|
||
const [leaveBalance, setLeaveBalance] = useState<any[]>([])
|
||
const [leaves, setLeaves] = useState<any[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [showModal, setShowModal] = useState(false)
|
||
const [submitting, setSubmitting] = useState(false)
|
||
|
||
const [form, setForm] = useState({
|
||
leaveType: 'ANNUAL',
|
||
startDate: '',
|
||
endDate: '',
|
||
leaveDate: '',
|
||
startTime: '',
|
||
endTime: '',
|
||
reason: '',
|
||
})
|
||
|
||
const load = () => {
|
||
setLoading(true)
|
||
Promise.all([portalAPI.getLeaveBalance(), portalAPI.getLeaves()])
|
||
.then(([balance, list]) => {
|
||
setLeaveBalance(balance)
|
||
setLeaves(list)
|
||
})
|
||
.catch(() => toast.error('فشل تحميل البيانات'))
|
||
.finally(() => setLoading(false))
|
||
}
|
||
|
||
useEffect(() => load(), [])
|
||
const toCompanyDateTime = (date: string, time: string) => {
|
||
return `${date}T${time}:00+03:00`
|
||
}
|
||
|
||
const handleSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
|
||
let payload: any = {
|
||
leaveType: form.leaveType,
|
||
reason: form.reason || undefined,
|
||
}
|
||
|
||
if (form.leaveType === 'ANNUAL') {
|
||
if (!form.startDate || !form.endDate) {
|
||
toast.error('أدخل تاريخ البداية والنهاية')
|
||
return
|
||
}
|
||
|
||
if (new Date(form.endDate) < new Date(form.startDate)) {
|
||
toast.error('تاريخ النهاية يجب أن يكون بعد البداية')
|
||
return
|
||
}
|
||
|
||
payload.startDate = form.startDate
|
||
payload.endDate = form.endDate
|
||
} else {
|
||
if (!form.leaveDate || !form.startTime || !form.endTime) {
|
||
toast.error('أدخل التاريخ والوقت للإجازة الساعية')
|
||
return
|
||
}
|
||
|
||
if (form.startTime >= form.endTime) {
|
||
toast.error('وقت النهاية يجب أن يكون بعد البداية')
|
||
return
|
||
}
|
||
|
||
payload.startDate = `${form.leaveDate}T${form.startTime}:00+03:00`
|
||
payload.endDate = `${form.leaveDate}T${form.endTime}:00+03:00`
|
||
}
|
||
|
||
setSubmitting(true)
|
||
|
||
portalAPI.submitLeaveRequest(payload)
|
||
.then(() => {
|
||
setShowModal(false)
|
||
setForm({
|
||
leaveType: 'ANNUAL',
|
||
startDate: '',
|
||
endDate: '',
|
||
leaveDate: '',
|
||
startTime: '',
|
||
endTime: '',
|
||
reason: '',
|
||
})
|
||
toast.success('تم إرسال طلب الإجازة')
|
||
load()
|
||
})
|
||
.catch(() => toast.error('فشل إرسال الطلب'))
|
||
.finally(() => setSubmitting(false))
|
||
}
|
||
|
||
if (loading) return <LoadingSpinner />
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
|
||
{/* HEADER */}
|
||
<div className="flex items-center justify-between">
|
||
<h1 className="text-2xl font-bold text-gray-900">إجازاتي</h1>
|
||
<button
|
||
onClick={() => setShowModal(true)}
|
||
className="flex items-center gap-2 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700"
|
||
>
|
||
<Plus className="h-4 w-4" />
|
||
طلب إجازة
|
||
</button>
|
||
</div>
|
||
|
||
{/* الرصيد */}
|
||
{leaveBalance.length > 0 && (
|
||
<div className="bg-white rounded-xl shadow p-6 border border-gray-100">
|
||
<h2 className="text-lg font-semibold text-gray-900 mb-4">رصيد الإجازات</h2>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
{leaveBalance.map((b) => (
|
||
<div key={b.leaveType} className="border rounded-lg p-4">
|
||
<p className="text-sm text-gray-600">
|
||
{b.leaveType === 'ANNUAL' ? 'سنوية' : b.leaveType}
|
||
</p>
|
||
<p className="text-2xl font-bold text-teal-600 mt-1">{b.available} يوم</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* الطلبات */}
|
||
<div className="bg-white rounded-xl shadow p-6 border border-gray-100">
|
||
<h2 className="text-lg font-semibold text-gray-900 mb-4">طلباتي</h2>
|
||
|
||
{leaves.length === 0 ? (
|
||
<p className="text-gray-500 text-center py-8">لا توجد طلبات</p>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{leaves.map((l) => {
|
||
const statusInfo = STATUS_MAP[l.status] || { label: l.status, color: 'bg-gray-100' }
|
||
|
||
return (
|
||
<div key={l.id} className="flex justify-between items-center py-3 border-b">
|
||
<div>
|
||
<p className="font-medium">
|
||
{l.leaveType === 'ANNUAL' ? 'سنوية' : 'ساعية'} -{' '}
|
||
{l.leaveType === 'HOURLY'
|
||
? `${new Date(l.startDate).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${new Date(l.endDate).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||
: `${l.days} يوم`}
|
||
</p>
|
||
|
||
<p className="text-sm text-gray-600">
|
||
{new Date(l.startDate).toLocaleDateString('ar-SA')} - {new Date(l.endDate).toLocaleDateString('ar-SA')}
|
||
</p>
|
||
</div>
|
||
|
||
<span className={`px-3 py-1 rounded-full text-xs ${statusInfo.color}`}>
|
||
{statusInfo.label}
|
||
</span>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* الفورم */}
|
||
<Modal isOpen={showModal} onClose={() => setShowModal(false)} title="طلب إجازة جديد">
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
|
||
{/* نوع الإجازة */}
|
||
<select
|
||
value={form.leaveType}
|
||
onChange={(e) =>
|
||
setForm({
|
||
leaveType: e.target.value,
|
||
startDate: '',
|
||
endDate: '',
|
||
leaveDate: '',
|
||
startTime: '',
|
||
endTime: '',
|
||
reason: '',
|
||
})
|
||
}
|
||
className="w-full px-3 py-2 border rounded-lg"
|
||
>
|
||
{LEAVE_TYPES.map((t) => (
|
||
<option key={t.value} value={t.value}>{t.label}</option>
|
||
))}
|
||
</select>
|
||
|
||
{/* سنوية */}
|
||
{form.leaveType === 'ANNUAL' ? (
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label className="text-sm">من تاريخ</label>
|
||
<input
|
||
type="date"
|
||
value={form.startDate}
|
||
onChange={(e) => setForm(p => ({ ...p, startDate: e.target.value }))}
|
||
className="border p-2 rounded w-full"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm">إلى تاريخ</label>
|
||
<input
|
||
type="date"
|
||
value={form.endDate}
|
||
onChange={(e) => setForm(p => ({ ...p, endDate: e.target.value }))}
|
||
className="border p-2 rounded w-full"
|
||
/>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
/* ساعية */
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<div>
|
||
<label className="text-sm">التاريخ</label>
|
||
<input
|
||
type="date"
|
||
value={form.leaveDate}
|
||
onChange={(e) => setForm(p => ({ ...p, leaveDate: e.target.value }))}
|
||
className="border p-2 rounded w-full"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm">من الساعة</label>
|
||
<select
|
||
value={form.startTime}
|
||
onChange={(e) => setForm(p => ({ ...p, startTime: e.target.value }))}
|
||
className="border p-2 rounded w-full"
|
||
>
|
||
<option value="">اختر الوقت</option>
|
||
{TIME_OPTIONS.map((time) => (
|
||
<option key={time} value={time}>{time}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm">إلى الساعة</label>
|
||
<select
|
||
value={form.endTime}
|
||
onChange={(e) => setForm(p => ({ ...p, endTime: e.target.value }))}
|
||
className="border p-2 rounded w-full"
|
||
>
|
||
<option value="">اختر الوقت</option>
|
||
{TIME_OPTIONS.map((time) => (
|
||
<option key={time} value={time}>{time}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* السبب */}
|
||
<textarea
|
||
placeholder="اكتب سبب الإجازة..."
|
||
value={form.reason}
|
||
onChange={(e) => setForm(p => ({ ...p, reason: e.target.value }))}
|
||
className="w-full border p-2 rounded"
|
||
/>
|
||
|
||
{/* أزرار */}
|
||
<div className="flex justify-end gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowModal(false)}
|
||
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg"
|
||
>
|
||
إلغاء
|
||
</button>
|
||
|
||
<button
|
||
type="submit"
|
||
disabled={submitting}
|
||
className="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 disabled:opacity-50"
|
||
>
|
||
{submitting ? 'جاري الإرسال...' : 'إرسال الطلب'}
|
||
</button>
|
||
</div>
|
||
|
||
</form>
|
||
</Modal>
|
||
</div>
|
||
)
|
||
} |