update HR modules
This commit is contained in:
@@ -5,13 +5,11 @@ import { portalAPI } from '@/lib/api/portal'
|
||||
import Modal from '@/components/Modal'
|
||||
import LoadingSpinner from '@/components/LoadingSpinner'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { Calendar, Plus } from 'lucide-react'
|
||||
import { Plus } from 'lucide-react'
|
||||
|
||||
const LEAVE_TYPES = [
|
||||
{ value: 'ANNUAL', label: 'إجازة سنوية' },
|
||||
{ value: 'SICK', label: 'إجازة مرضية' },
|
||||
{ value: 'EMERGENCY', label: 'طوارئ' },
|
||||
{ value: 'UNPAID', label: 'بدون راتب' },
|
||||
{ value: 'HOURLY', label: 'إجازة ساعية' },
|
||||
]
|
||||
|
||||
const STATUS_MAP: Record<string, { label: string; color: string }> = {
|
||||
@@ -26,7 +24,16 @@ export default function PortalLeavePage() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [form, setForm] = useState({ leaveType: 'ANNUAL', startDate: '', endDate: '', reason: '' })
|
||||
|
||||
const [form, setForm] = useState({
|
||||
leaveType: 'ANNUAL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
leaveDate: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
reason: '',
|
||||
})
|
||||
|
||||
const load = () => {
|
||||
setLoading(true)
|
||||
@@ -43,24 +50,54 @@ export default function PortalLeavePage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!form.startDate || !form.endDate) {
|
||||
toast.error('أدخل تاريخ البداية والنهاية')
|
||||
return
|
||||
}
|
||||
if (new Date(form.endDate) < new Date(form.startDate)) {
|
||||
toast.error('تاريخ النهاية يجب أن يكون بعد تاريخ البداية')
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
portalAPI.submitLeaveRequest({
|
||||
|
||||
let payload: any = {
|
||||
leaveType: form.leaveType,
|
||||
startDate: form.startDate,
|
||||
endDate: form.endDate,
|
||||
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`
|
||||
payload.endDate = `${form.leaveDate}T${form.endTime}:00`
|
||||
}
|
||||
|
||||
setSubmitting(true)
|
||||
|
||||
portalAPI.submitLeaveRequest(payload)
|
||||
.then(() => {
|
||||
setShowModal(false)
|
||||
setForm({ leaveType: 'ANNUAL', startDate: '', endDate: '', reason: '' })
|
||||
setForm({
|
||||
leaveType: 'ANNUAL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
leaveDate: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
reason: '',
|
||||
})
|
||||
toast.success('تم إرسال طلب الإجازة')
|
||||
load()
|
||||
})
|
||||
@@ -72,6 +109,8 @@ export default function PortalLeavePage() {
|
||||
|
||||
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
|
||||
@@ -83,6 +122,7 @@ export default function PortalLeavePage() {
|
||||
</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>
|
||||
@@ -90,36 +130,42 @@ export default function PortalLeavePage() {
|
||||
{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 === 'SICK' ? 'مرضية' : b.leaveType}
|
||||
{b.leaveType === 'ANNUAL' ? 'سنوية' : b.leaveType}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-teal-600 mt-1">{b.available} يوم</p>
|
||||
<p className="text-xs text-gray-500">من {b.totalDays + b.carriedOver} (مستخدم: {b.usedDays})</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>
|
||||
<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 last:border-0">
|
||||
<div key={l.id} className="flex justify-between items-center py-3 border-b">
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{l.leaveType === 'ANNUAL' ? 'سنوية' : l.leaveType === 'SICK' ? 'مرضية' : l.leaveType} - {l.days} يوم
|
||||
{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>
|
||||
{l.rejectedReason && <p className="text-sm text-red-600">سبب الرفض: {l.rejectedReason}</p>}
|
||||
</div>
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${statusInfo.color}`}>
|
||||
|
||||
<span className={`px-3 py-1 rounded-full text-xs ${statusInfo.color}`}>
|
||||
{statusInfo.label}
|
||||
</span>
|
||||
</div>
|
||||
@@ -129,61 +175,118 @@ export default function PortalLeavePage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* الفورم */}
|
||||
<Modal isOpen={showModal} onClose={() => setShowModal(false)} title="طلب إجازة جديد">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">نوع الإجازة</label>
|
||||
<select
|
||||
value={form.leaveType}
|
||||
onChange={(e) => setForm((p) => ({ ...p, leaveType: e.target.value }))}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
||||
>
|
||||
{LEAVE_TYPES.map((t) => (
|
||||
<option key={t.value} value={t.value}>{t.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">من تاريخ</label>
|
||||
<input
|
||||
type="date"
|
||||
value={form.startDate}
|
||||
onChange={(e) => setForm((p) => ({ ...p, startDate: e.target.value }))}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
||||
required
|
||||
/>
|
||||
|
||||
{/* نوع الإجازة */}
|
||||
<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>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">إلى تاريخ</label>
|
||||
<input
|
||||
type="date"
|
||||
value={form.endDate}
|
||||
onChange={(e) => setForm((p) => ({ ...p, endDate: e.target.value }))}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
||||
required
|
||||
/>
|
||||
) : (
|
||||
/* ساعية */
|
||||
<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>
|
||||
<input
|
||||
type="time"
|
||||
value={form.startTime}
|
||||
onChange={(e) => setForm(p => ({ ...p, startTime: e.target.value }))}
|
||||
className="border p-2 rounded w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm">إلى الساعة</label>
|
||||
<input
|
||||
type="time"
|
||||
value={form.endTime}
|
||||
onChange={(e) => setForm(p => ({ ...p, endTime: e.target.value }))}
|
||||
className="border p-2 rounded w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">السبب</label>
|
||||
<textarea
|
||||
value={form.reason}
|
||||
onChange={(e) => setForm((p) => ({ ...p, reason: e.target.value }))}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
||||
rows={3}
|
||||
/>
|
||||
</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
|
||||
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">
|
||||
|
||||
<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>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user