import React, { useState } from 'react' import { FaCalculator, FaFileAlt, FaDownload, FaEye, FaEdit, FaPlus, FaTimes, FaSave, FaCheck, FaBan, FaUsers, } from 'react-icons/fa' import { HrPayroll, PayrollStatusEnum } from '../../../types/hr' import DataTable, { Column } from '../../../components/common/DataTable' import { mockEmployees } from '../../../mocks/mockEmployees' import { mockPayrolls } from '../../../mocks/mockPayrolls' import { mockDepartments } from '../../../mocks/mockDepartments' import Widget from '../../../components/common/Widget' import { getPayrollStatusColor, getPayrollStatusText } from '../../../utils/erp' import { Container } from '@/components/shared' // Mock data - replace with actual data fetching const PayrollManagement: React.FC = () => { const [payrolls, setPayrolls] = useState(mockPayrolls) const [selectedStatus, setSelectedStatus] = useState('all') const [selectedPeriod, setSelectedPeriod] = useState('all') const [selectedDepartment, setSelectedDepartment] = useState('all') const [showModal, setShowModal] = useState(false) const [showBulkModal, setShowBulkModal] = useState(false) const [modalType, setModalType] = useState<'add' | 'edit' | 'view'>('add') const [selectedPayroll, setSelectedPayroll] = useState(null) const [searchTerm, setSearchTerm] = useState('') const [formData, setFormData] = useState>({ period: '', baseSalary: 0, overtime: 0, bonus: 0, allowances: [], deductions: [], status: PayrollStatusEnum.Draft, }) const handleAdd = () => { setModalType('add') setFormData({ period: new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), baseSalary: 0, overtime: 0, bonus: 0, allowances: [], deductions: [], status: PayrollStatusEnum.Draft, }) setSelectedPayroll(null) setShowModal(true) } const handleEdit = (payroll: HrPayroll) => { setModalType('edit') setFormData(payroll) setSelectedPayroll(payroll) setShowModal(true) } const handleView = (payroll: HrPayroll) => { setModalType('view') setFormData(payroll) setSelectedPayroll(payroll) setShowModal(true) } const handleCalculate = (payroll: HrPayroll) => { // Calculate gross and net salary const totalAllowances = payroll.allowances.reduce( (total, allowance) => total + allowance.amount, 0, ) const totalDeductions = payroll.deductions.reduce( (total, deduction) => total + deduction.amount, 0, ) const grossSalary = payroll.baseSalary + totalAllowances + payroll.overtime + payroll.bonus const tax = grossSalary * 0.15 // 15% tax rate (simplified) const socialSecurity = grossSalary * 0.14 // 14% social security (simplified) const netSalary = grossSalary - tax - socialSecurity - totalDeductions const updatedPayroll: HrPayroll = { ...payroll, grossSalary, tax, socialSecurity, netSalary, status: PayrollStatusEnum.Calculated, lastModificationTime: new Date(), } setPayrolls((prevPayrolls) => prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)), ) alert( `Bordro hesaplandı:\nBrüt Maaş: ₺${grossSalary.toLocaleString()}\nNet Maaş: ₺${netSalary.toLocaleString()}`, ) } const handleApprove = (payroll: HrPayroll) => { const updatedPayroll: HrPayroll = { ...payroll, status: PayrollStatusEnum.Approved, lastModificationTime: new Date(), } setPayrolls((prevPayrolls) => prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)), ) alert(`Bordro onaylandı: ${payroll.employee?.fullName}`) } const handleReject = (payroll: HrPayroll) => { const updatedPayroll: HrPayroll = { ...payroll, status: PayrollStatusEnum.Cancelled, lastModificationTime: new Date(), } setPayrolls((prevPayrolls) => prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)), ) alert(`Bordro reddedildi: ${payroll.employee?.fullName}`) } const handleBulkEntry = () => { setShowBulkModal(true) } const handleExport = (payroll: HrPayroll) => { // Create a simple CSV export const csvContent = [ ['Personel Kodu', payroll.employee?.code || ''], ['Personel Adı', payroll.employee?.fullName || ''], ['Dönem', payroll.period], ['Temel Maaş', `₺${payroll.baseSalary.toLocaleString()}`], ['Mesai Ücreti', `₺${payroll.overtime.toLocaleString()}`], ['Prim', `₺${payroll.bonus.toLocaleString()}`], ['Brüt Maaş', `₺${payroll.grossSalary.toLocaleString()}`], ['Vergi', `₺${payroll.tax.toLocaleString()}`], ['SGK', `₺${payroll.socialSecurity.toLocaleString()}`], ['Net Maaş', `₺${payroll.netSalary.toLocaleString()}`], ['Durum', getPayrollStatusText(payroll.status)], ] .map((row) => row.join(',')) .join('\n') const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;', }) const link = document.createElement('a') const url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', `bordro_${payroll.employee?.code}_${payroll.period}.csv`) link.style.visibility = 'hidden' document.body.appendChild(link) link.click() document.body.removeChild(link) } const handleSubmit = () => { if (modalType === 'add') { const newPayroll: HrPayroll = { id: Date.now().toString(), employeeId: formData.employeeId || '', employee: mockEmployees.find((emp) => emp.id === formData.employeeId), period: formData.period || '', baseSalary: formData.baseSalary || 0, allowances: formData.allowances || [], deductions: formData.deductions || [], overtime: formData.overtime || 0, bonus: formData.bonus || 0, grossSalary: 0, netSalary: 0, tax: 0, socialSecurity: 0, status: PayrollStatusEnum.Draft, creationTime: new Date(), lastModificationTime: new Date(), } setPayrolls((prevPayrolls) => [...prevPayrolls, newPayroll]) } else if (modalType === 'edit' && selectedPayroll) { const updatedPayroll: HrPayroll = { ...selectedPayroll, ...formData, lastModificationTime: new Date(), } setPayrolls((prevPayrolls) => prevPayrolls.map((p) => (p.id === selectedPayroll.id ? updatedPayroll : p)), ) } setShowModal(false) setSelectedPayroll(null) setFormData({ period: '', baseSalary: 0, overtime: 0, bonus: 0, allowances: [], deductions: [], status: PayrollStatusEnum.Draft, }) } const handleCancel = () => { setShowModal(false) setSelectedPayroll(null) setFormData({ period: '', baseSalary: 0, overtime: 0, bonus: 0, allowances: [], deductions: [], status: PayrollStatusEnum.Draft, }) } // Calculate gross and net salary helper function const calculateSalaries = (data: Partial) => { const totalAllowances = (data.allowances || []).reduce( (total, allowance) => total + allowance.amount, 0, ) const totalDeductions = (data.deductions || []).reduce( (total, deduction) => total + deduction.amount, 0, ) const grossSalary = (data.baseSalary || 0) + totalAllowances + (data.overtime || 0) + (data.bonus || 0) const tax = grossSalary * 0.15 // 15% tax rate (simplified) const socialSecurity = grossSalary * 0.14 // 14% social security (simplified) const netSalary = grossSalary - tax - socialSecurity - totalDeductions return { grossSalary, tax, socialSecurity, netSalary, } } // Real-time calculation for form data const calculatedValues = calculateSalaries(formData) const filteredPayrolls = payrolls.filter((payroll) => { if (selectedStatus !== 'all' && payroll.status !== selectedStatus) { return false } if (selectedPeriod !== 'all' && payroll.period !== selectedPeriod) { return false } if (selectedDepartment !== 'all' && payroll.employee?.department?.name !== selectedDepartment) { return false } if (searchTerm.trim() !== '') { if (payroll.employeeId !== searchTerm) { return false } } return true }) const columns: Column[] = [ { key: 'employee', header: 'Personel', render: (payroll: HrPayroll) => (
{payroll.employee?.fullName}
{payroll.employee?.code}
), }, { key: 'period', header: 'Dönem', render: (payroll: HrPayroll) => payroll.period, }, { key: 'baseSalary', header: 'Temel Maaş', render: (payroll: HrPayroll) => `₺${payroll.baseSalary.toLocaleString()}`, }, { key: 'allowances', header: 'Ödemeler', render: (payroll: HrPayroll) => { const totalAllowances = payroll.allowances.reduce( (total, allowance) => total + allowance.amount, 0, ) return `₺${totalAllowances.toLocaleString()}` }, }, { key: 'deductions', header: 'Kesintiler', render: (payroll: HrPayroll) => { const totalDeductions = payroll.deductions.reduce( (total, deduction) => total + deduction.amount, 0, ) return `₺${totalDeductions.toLocaleString()}` }, }, { key: 'grossSalary', header: 'Brüt Maaş', render: (payroll: HrPayroll) => (
₺{payroll.grossSalary.toLocaleString()}
), }, { key: 'netSalary', header: 'Net Maaş', render: (payroll: HrPayroll) => (
₺{payroll.netSalary.toLocaleString()}
), }, { key: 'status', header: 'Durum', render: (payroll: HrPayroll) => ( {getPayrollStatusText(payroll.status)} ), }, { key: 'actions', header: 'İşlemler', render: (payroll: HrPayroll) => (
{payroll.status === PayrollStatusEnum.Draft && ( )} {payroll.status === PayrollStatusEnum.Calculated && ( <> )} {(payroll.status === PayrollStatusEnum.Approved || payroll.status === PayrollStatusEnum.Paid) && ( )}
), }, ] // Calculate statistics const stats = { total: payrolls.length, totalGross: payrolls.reduce((total, p) => total + p.grossSalary, 0), totalNet: payrolls.reduce((total, p) => total + p.netSalary, 0), totalTax: payrolls.reduce((total, p) => total + p.tax, 0), pending: payrolls.filter( (p) => p.status === PayrollStatusEnum.Draft || p.status === PayrollStatusEnum.Calculated, ).length, } // Get unique periods for filter const periods = [...new Set(payrolls.map((p) => p.period))].sort().reverse() return (
{/* Header */}

Maaş, Prim, Bordro Yönetimi

Personel ödemelerini hesaplayın ve yönetin

{/* Stats Cards */}
{/* Filters */}
{/* Data Table */}
{filteredPayrolls.length === 0 && (

Bordro bulunamadı

Seçilen kriterlere uygun bordro bulunmamaktadır.

)}
{/* Modal */} {showModal && (

{modalType === 'add' && 'Yeni Bordro Ekle'} {modalType === 'edit' && 'Bordro Düzenle'} {modalType === 'view' && 'Bordro Detayları'}

{/* Employee Selection */} {modalType === 'add' && (
)} {/* Employee Info (for edit/view) */} {(modalType === 'edit' || modalType === 'view') && formData.employee && (
Personel
{formData.employee.code} - {formData.employee.fullName}
)} {/* Period */}
setFormData({ ...formData, period: e.target.value })} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={modalType === 'view'} />
{/* Base Salary */}
setFormData({ ...formData, baseSalary: Number(e.target.value), }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={modalType === 'view'} />
{/* Overtime */}
setFormData({ ...formData, overtime: Number(e.target.value), }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={modalType === 'view'} />
{/* Bonus */}
setFormData({ ...formData, bonus: Number(e.target.value) })} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={modalType === 'view'} />
{/* Allowances */} {modalType !== 'view' && (
{formData.allowances?.map((allowance, index) => (
{ const updatedAllowances = [...(formData.allowances || [])] updatedAllowances[index] = { ...allowance, name: e.target.value, } setFormData({ ...formData, allowances: updatedAllowances, }) }} className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" /> { const updatedAllowances = [...(formData.allowances || [])] updatedAllowances[index] = { ...allowance, amount: Number(e.target.value), } setFormData({ ...formData, allowances: updatedAllowances, }) }} className="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
))} {(!formData.allowances || formData.allowances.length === 0) && (

Henüz ödeme eklenmedi

)}
)} {/* Deductions */} {modalType !== 'view' && (
{formData.deductions?.map((deduction, index) => (
{ const updatedDeductions = [...(formData.deductions || [])] updatedDeductions[index] = { ...deduction, name: e.target.value, } setFormData({ ...formData, deductions: updatedDeductions, }) }} className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" /> { const updatedDeductions = [...(formData.deductions || [])] updatedDeductions[index] = { ...deduction, amount: Number(e.target.value), } setFormData({ ...formData, deductions: updatedDeductions, }) }} className="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
))} {(!formData.deductions || formData.deductions.length === 0) && (

Henüz kesinti eklenmedi

)}
)} {/* Real-time calculation preview for add/edit modes */} {modalType !== 'view' && (
Hesaplama Önizlemesi
Brüt Maaş
₺{calculatedValues.grossSalary.toLocaleString()}
Temel: ₺{(formData.baseSalary || 0).toLocaleString()} + Ödemeler: ₺ {(formData.allowances || []) .reduce((total, allowance) => total + allowance.amount, 0) .toLocaleString()}{' '} + Mesai: ₺{(formData.overtime || 0).toLocaleString()} + Prim: ₺ {(formData.bonus || 0).toLocaleString()}
Net Maaş
₺{calculatedValues.netSalary.toLocaleString()}
Brüt - Vergi: ₺{calculatedValues.tax.toLocaleString()} - SGK: ₺ {calculatedValues.socialSecurity.toLocaleString()} - Kesintiler: ₺ {(formData.deductions || []) .reduce((total, deduction) => total + deduction.amount, 0) .toLocaleString()}
)} {/* View mode - show allowances and deductions */} {modalType === 'view' && ( <>
{formData.allowances && formData.allowances.length > 0 ? (
{formData.allowances.map((allowance) => (
{allowance.name} ₺{allowance.amount.toLocaleString()}
))}
Toplam: ₺ {formData.allowances .reduce((total, allowance) => total + allowance.amount, 0) .toLocaleString()}
) : ( Ödeme yok )}
{formData.deductions && formData.deductions.length > 0 ? (
{formData.deductions.map((deduction) => (
{deduction.name} ₺{deduction.amount.toLocaleString()}
))}
Toplam: ₺ {formData.deductions .reduce((total, deduction) => total + deduction.amount, 0) .toLocaleString()}
) : ( Kesinti yok )}
)} {/* Calculated fields (show only in view/edit mode for existing payrolls) */} {modalType === 'view' && ( <> {/* Status */}
{getPayrollStatusText(formData.status || PayrollStatusEnum.Draft)}
₺{formData.grossSalary?.toLocaleString()}
₺{formData.netSalary?.toLocaleString()}
₺{formData.tax?.toLocaleString()}
₺{formData.socialSecurity?.toLocaleString()}
)}
{/* Modal Actions */} {modalType !== 'view' && (
)} {modalType === 'view' && (
)}
)} {/* Bulk Entry Modal */} {showBulkModal && ( setShowBulkModal(false)} onSubmit={(payrolls) => { setPayrolls((prev) => [...prev, ...payrolls]) setShowBulkModal(false) }} /> )}
) } // Bulk Payroll Entry Component interface BulkPayrollEntryProps { onClose: () => void onSubmit: (payrolls: HrPayroll[]) => void } const BulkPayrollEntry: React.FC = ({ onClose, onSubmit }) => { const [selectedDepartment, setSelectedDepartment] = useState('') const [selectedEmployees, setSelectedEmployees] = useState([]) const [period, setPeriod] = useState( new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), ) const [bulkData, setBulkData] = useState({ baseSalaryIncrease: 0, overtimeHours: 0, overtimeRate: 50, bonusAmount: 0, massBonus: 0, }) // Get unique departments from mockDepartments and employees const departments = [ ...new Set([ ...mockDepartments.map((dept) => dept.name), ...mockEmployees.map((emp) => emp.department?.name).filter(Boolean), ]), ].sort() // Get employees by department const filteredEmployees = selectedDepartment ? mockEmployees.filter((emp) => emp.department?.name === selectedDepartment) : [] // Get selected employees data const selectedEmployeesData = filteredEmployees.filter((emp) => selectedEmployees.includes(emp.id), ) // Handle department change - reset selected employees and auto-select all const handleDepartmentChange = (departmentName: string) => { setSelectedDepartment(departmentName) // Auto-select all employees when department is selected if (departmentName) { const employeesInDept = mockEmployees.filter((emp) => emp.department?.name === departmentName) setSelectedEmployees(employeesInDept.map((emp) => emp.id)) } else { setSelectedEmployees([]) } } // Handle select all employees const handleSelectAll = (checked: boolean) => { if (checked) { setSelectedEmployees(filteredEmployees.map((emp) => emp.id)) } else { setSelectedEmployees([]) } } // Handle individual employee selection const handleEmployeeSelect = (employeeId: string, checked: boolean) => { if (checked) { setSelectedEmployees((prev) => [...prev, employeeId]) } else { setSelectedEmployees((prev) => prev.filter((id) => id !== employeeId)) } } const handleBulkSubmit = () => { if (!selectedDepartment || selectedEmployeesData.length === 0) { alert('Lütfen bir departman ve en az bir personel seçin') return } const newPayrolls: HrPayroll[] = selectedEmployeesData.map((employee) => { const baseSalary = employee.baseSalary + bulkData.baseSalaryIncrease const overtime = bulkData.overtimeHours * bulkData.overtimeRate const bonus = bulkData.bonusAmount + bulkData.massBonus return { id: `bulk_${Date.now()}_${employee.id}`, employeeId: employee.id, employee: employee, period: period, baseSalary: baseSalary, allowances: [], deductions: [], overtime: overtime, bonus: bonus, grossSalary: 0, netSalary: 0, tax: 0, socialSecurity: 0, status: PayrollStatusEnum.Draft, creationTime: new Date(), lastModificationTime: new Date(), } }) onSubmit(newPayrolls) } return (

Toplu Bordro Girişi

{/* Department and Period */}
setPeriod(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
{/* Bulk Settings */}

Toplu Ayarlar

setBulkData({ ...bulkData, baseSalaryIncrease: Number(e.target.value), }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
setBulkData({ ...bulkData, overtimeHours: Number(e.target.value), }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
setBulkData({ ...bulkData, overtimeRate: Number(e.target.value), }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
setBulkData({ ...bulkData, massBonus: Number(e.target.value), }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
{/* Employee Selection */} {selectedDepartment && filteredEmployees.length > 0 && (

Personel Seçimi ({filteredEmployees.length} kişi)

{filteredEmployees.map((employee) => ( ))}
Seç Personel Kodu Ad Soyad Mevcut Maaş Yeni Maaş
handleEmployeeSelect(employee.id, e.target.checked)} className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" /> {employee.code} {employee.fullName} ₺{employee.baseSalary.toLocaleString()} ₺{(employee.baseSalary + bulkData.baseSalaryIncrease).toLocaleString()}
{selectedEmployees.length > 0 && (

{selectedEmployees.length} personel seçildi. Toplam mevcut maaş:{' '} ₺ {selectedEmployeesData .reduce((total, emp) => total + emp.baseSalary, 0) .toLocaleString()}

)}
)}
{/* Modal Actions */}
) } export default PayrollManagement