Files
zerp/frontend/src/app/portal/salaries/page.tsx
Talal Sharabi 72ed9a2ff5 feat(hr): Complete HR module with Employee Portal, Loans, Leave, Purchase Requests, Contracts
- Database: Add Loan, LoanInstallment, PurchaseRequest, LeaveEntitlement, EmployeeContract models
- Database: Extend Attendance with ZK Tico fields (sourceDeviceId, externalId, rawData)
- Database: Add Employee.attendancePin for device mapping
- Backend: HR admin - Loans, Purchase Requests, Leave entitlements, Employee contracts CRUD
- Backend: Leave reject, bulk attendance sync (ZK Tico ready)
- Backend: Employee Portal API - scoped by employeeId (loans, leaves, purchase-requests, attendance, salaries)
- Frontend: Employee Portal - dashboard, loans, leave, purchase-requests, attendance, salaries
- Frontend: HR Admin - new tabs for Leaves, Loans, Purchase Requests, Contracts (approve/reject)
- Dashboard: Add My Portal link
- No destructive schema changes; additive migrations only

Made-with: Cursor
2026-03-04 19:44:09 +04:00

66 lines
2.8 KiB
TypeScript
Raw 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 { portalAPI, type Salary } from '@/lib/api/portal'
import LoadingSpinner from '@/components/LoadingSpinner'
import { DollarSign } from 'lucide-react'
const MONTHS_AR = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']
export default function PortalSalariesPage() {
const [salaries, setSalaries] = useState<Salary[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
portalAPI.getSalaries()
.then(setSalaries)
.catch(() => setSalaries([]))
.finally(() => setLoading(false))
}, [])
if (loading) return <LoadingSpinner />
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">رواتبي</h1>
{salaries.length === 0 ? (
<div className="bg-white rounded-xl shadow p-12 text-center text-gray-500">
<DollarSign className="h-12 w-12 mx-auto mb-4 text-gray-300" />
<p>لا توجد سجلات رواتب</p>
</div>
) : (
<div className="space-y-4">
{salaries.map((s) => (
<div key={s.id} className="bg-white rounded-xl shadow p-6 border border-gray-100">
<div className="flex justify-between items-start">
<div>
<p className="font-semibold text-gray-900">
{MONTHS_AR[s.month - 1]} {s.year}
</p>
<p className="text-2xl font-bold text-teal-600 mt-1">
{Number(s.netSalary).toLocaleString()} ر.س
</p>
<div className="mt-2 text-sm text-gray-600 space-y-1">
<p>الأساس: {Number(s.basicSalary).toLocaleString()} | البدلات: {Number(s.allowances).toLocaleString()} | الخصومات: {Number(s.deductions).toLocaleString()}</p>
<p>عمولة: {Number(s.commissions).toLocaleString()} | إضافي: {Number(s.overtimePay).toLocaleString()}</p>
</div>
</div>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
s.status === 'PAID' ? 'bg-green-100 text-green-800' :
s.status === 'APPROVED' ? 'bg-blue-100 text-blue-800' : 'bg-amber-100 text-amber-800'
}`}>
{s.status === 'PAID' ? 'مدفوع' : s.status === 'APPROVED' ? 'معتمد' : 'قيد المعالجة'}
</span>
</div>
{s.paidDate && (
<p className="text-xs text-gray-500 mt-2">تاريخ الدفع: {new Date(s.paidDate).toLocaleDateString('ar-SA')}</p>
)}
</div>
))}
</div>
)}
</div>
)
}