erp-platform/ui/src/views/hr/components/PayrollManagement.tsx

1346 lines
51 KiB
TypeScript
Raw Normal View History

2025-09-15 19:46:52 +00:00
import React, { useState } from 'react'
2025-09-15 09:31:47 +00:00
import {
FaCalculator,
FaFileAlt,
FaDownload,
FaEye,
FaEdit,
FaPlus,
FaTimes,
FaSave,
FaCheck,
FaBan,
FaUsers,
2025-09-15 19:46:52 +00:00
} 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'
2025-09-15 09:31:47 +00:00
// Mock data - replace with actual data fetching
const PayrollManagement: React.FC = () => {
2025-09-15 19:46:52 +00:00
const [payrolls, setPayrolls] = useState<HrPayroll[]>(mockPayrolls)
const [selectedStatus, setSelectedStatus] = useState<string>('all')
const [selectedPeriod, setSelectedPeriod] = useState<string>('all')
const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
const [showModal, setShowModal] = useState<boolean>(false)
const [showBulkModal, setShowBulkModal] = useState<boolean>(false)
const [modalType, setModalType] = useState<'add' | 'edit' | 'view'>('add')
const [selectedPayroll, setSelectedPayroll] = useState<HrPayroll | null>(null)
const [searchTerm, setSearchTerm] = useState('')
2025-09-15 09:31:47 +00:00
const [formData, setFormData] = useState<Partial<HrPayroll>>({
2025-09-15 19:46:52 +00:00
period: '',
2025-09-15 09:31:47 +00:00
baseSalary: 0,
overtime: 0,
bonus: 0,
allowances: [],
deductions: [],
status: PayrollStatusEnum.Draft,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
const handleAdd = () => {
2025-09-15 19:46:52 +00:00
setModalType('add')
2025-09-15 09:31:47 +00:00
setFormData({
2025-09-15 19:46:52 +00:00
period: new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'),
2025-09-15 09:31:47 +00:00
baseSalary: 0,
overtime: 0,
bonus: 0,
allowances: [],
deductions: [],
status: PayrollStatusEnum.Draft,
2025-09-15 19:46:52 +00:00
})
setSelectedPayroll(null)
setShowModal(true)
}
2025-09-15 09:31:47 +00:00
const handleEdit = (payroll: HrPayroll) => {
2025-09-15 19:46:52 +00:00
setModalType('edit')
setFormData(payroll)
setSelectedPayroll(payroll)
setShowModal(true)
}
2025-09-15 09:31:47 +00:00
const handleView = (payroll: HrPayroll) => {
2025-09-15 19:46:52 +00:00
setModalType('view')
setFormData(payroll)
setSelectedPayroll(payroll)
setShowModal(true)
}
2025-09-15 09:31:47 +00:00
const handleCalculate = (payroll: HrPayroll) => {
// Calculate gross and net salary
const totalAllowances = payroll.allowances.reduce(
(total, allowance) => total + allowance.amount,
2025-09-15 19:46:52 +00:00
0,
)
2025-09-15 09:31:47 +00:00
const totalDeductions = payroll.deductions.reduce(
(total, deduction) => total + deduction.amount,
2025-09-15 19:46:52 +00:00
0,
)
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
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
2025-09-15 09:31:47 +00:00
const updatedPayroll: HrPayroll = {
...payroll,
grossSalary,
tax,
socialSecurity,
netSalary,
status: PayrollStatusEnum.Calculated,
lastModificationTime: new Date(),
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setPayrolls((prevPayrolls) =>
2025-09-15 19:46:52 +00:00
prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)),
)
2025-09-15 09:31:47 +00:00
alert(
2025-09-15 19:46:52 +00:00
`Bordro hesaplandı:\nBrüt Maaş: ₺${grossSalary.toLocaleString()}\nNet Maaş: ₺${netSalary.toLocaleString()}`,
)
}
2025-09-15 09:31:47 +00:00
const handleApprove = (payroll: HrPayroll) => {
const updatedPayroll: HrPayroll = {
...payroll,
status: PayrollStatusEnum.Approved,
lastModificationTime: new Date(),
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setPayrolls((prevPayrolls) =>
2025-09-15 19:46:52 +00:00
prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)),
)
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
alert(`Bordro onaylandı: ${payroll.employee?.fullName}`)
}
2025-09-15 09:31:47 +00:00
const handleReject = (payroll: HrPayroll) => {
const updatedPayroll: HrPayroll = {
...payroll,
status: PayrollStatusEnum.Cancelled,
lastModificationTime: new Date(),
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setPayrolls((prevPayrolls) =>
2025-09-15 19:46:52 +00:00
prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)),
)
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
alert(`Bordro reddedildi: ${payroll.employee?.fullName}`)
}
2025-09-15 09:31:47 +00:00
const handleBulkEntry = () => {
2025-09-15 19:46:52 +00:00
setShowBulkModal(true)
}
2025-09-15 09:31:47 +00:00
const handleExport = (payroll: HrPayroll) => {
// Create a simple CSV export
const csvContent = [
2025-09-15 19:46:52 +00:00
['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)],
2025-09-15 09:31:47 +00:00
]
2025-09-15 19:46:52 +00:00
.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)
}
2025-09-15 09:31:47 +00:00
const handleSubmit = () => {
2025-09-15 19:46:52 +00:00
if (modalType === 'add') {
2025-09-15 09:31:47 +00:00
const newPayroll: HrPayroll = {
id: Date.now().toString(),
2025-09-15 19:46:52 +00:00
employeeId: formData.employeeId || '',
2025-09-15 09:31:47 +00:00
employee: mockEmployees.find((emp) => emp.id === formData.employeeId),
2025-09-15 19:46:52 +00:00
period: formData.period || '',
2025-09-15 09:31:47 +00:00
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(),
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
setPayrolls((prevPayrolls) => [...prevPayrolls, newPayroll])
} else if (modalType === 'edit' && selectedPayroll) {
2025-09-15 09:31:47 +00:00
const updatedPayroll: HrPayroll = {
...selectedPayroll,
...formData,
lastModificationTime: new Date(),
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setPayrolls((prevPayrolls) =>
2025-09-15 19:46:52 +00:00
prevPayrolls.map((p) => (p.id === selectedPayroll.id ? updatedPayroll : p)),
)
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
setShowModal(false)
setSelectedPayroll(null)
2025-09-15 09:31:47 +00:00
setFormData({
2025-09-15 19:46:52 +00:00
period: '',
2025-09-15 09:31:47 +00:00
baseSalary: 0,
overtime: 0,
bonus: 0,
allowances: [],
deductions: [],
status: PayrollStatusEnum.Draft,
2025-09-15 19:46:52 +00:00
})
}
2025-09-15 09:31:47 +00:00
const handleCancel = () => {
2025-09-15 19:46:52 +00:00
setShowModal(false)
setSelectedPayroll(null)
2025-09-15 09:31:47 +00:00
setFormData({
2025-09-15 19:46:52 +00:00
period: '',
2025-09-15 09:31:47 +00:00
baseSalary: 0,
overtime: 0,
bonus: 0,
allowances: [],
deductions: [],
status: PayrollStatusEnum.Draft,
2025-09-15 19:46:52 +00:00
})
}
2025-09-15 09:31:47 +00:00
// Calculate gross and net salary helper function
const calculateSalaries = (data: Partial<HrPayroll>) => {
const totalAllowances = (data.allowances || []).reduce(
(total, allowance) => total + allowance.amount,
2025-09-15 19:46:52 +00:00
0,
)
2025-09-15 09:31:47 +00:00
const totalDeductions = (data.deductions || []).reduce(
(total, deduction) => total + deduction.amount,
2025-09-15 19:46:52 +00:00
0,
)
2025-09-15 09:31:47 +00:00
const grossSalary =
2025-09-15 19:46:52 +00:00
(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
2025-09-15 09:31:47 +00:00
return {
grossSalary,
tax,
socialSecurity,
netSalary,
2025-09-15 19:46:52 +00:00
}
}
2025-09-15 09:31:47 +00:00
// Real-time calculation for form data
2025-09-15 19:46:52 +00:00
const calculatedValues = calculateSalaries(formData)
2025-09-15 09:31:47 +00:00
const filteredPayrolls = payrolls.filter((payroll) => {
2025-09-15 19:46:52 +00:00
if (selectedStatus !== 'all' && payroll.status !== selectedStatus) {
return false
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
if (selectedPeriod !== 'all' && payroll.period !== selectedPeriod) {
return false
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
if (selectedDepartment !== 'all' && payroll.employee?.department?.name !== selectedDepartment) {
return false
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
if (searchTerm.trim() !== '') {
2025-09-15 09:31:47 +00:00
if (payroll.employeeId !== searchTerm) {
2025-09-15 19:46:52 +00:00
return false
2025-09-15 09:31:47 +00:00
}
}
2025-09-15 19:46:52 +00:00
return true
})
2025-09-15 09:31:47 +00:00
const columns: Column<HrPayroll>[] = [
{
2025-09-15 19:46:52 +00:00
key: 'employee',
header: 'Personel',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => (
<div>
2025-09-15 19:46:52 +00:00
<div className="font-medium text-gray-900">{payroll.employee?.fullName}</div>
2025-09-15 09:31:47 +00:00
<div className="text-sm text-gray-500">{payroll.employee?.code}</div>
</div>
),
},
{
2025-09-15 19:46:52 +00:00
key: 'period',
header: 'Dönem',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => payroll.period,
},
{
2025-09-15 19:46:52 +00:00
key: 'baseSalary',
header: 'Temel Maaş',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => `${payroll.baseSalary.toLocaleString()}`,
},
{
2025-09-15 19:46:52 +00:00
key: 'allowances',
header: 'Ödemeler',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => {
const totalAllowances = payroll.allowances.reduce(
(total, allowance) => total + allowance.amount,
2025-09-15 19:46:52 +00:00
0,
)
return `${totalAllowances.toLocaleString()}`
2025-09-15 09:31:47 +00:00
},
},
{
2025-09-15 19:46:52 +00:00
key: 'deductions',
header: 'Kesintiler',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => {
const totalDeductions = payroll.deductions.reduce(
(total, deduction) => total + deduction.amount,
2025-09-15 19:46:52 +00:00
0,
)
return `${totalDeductions.toLocaleString()}`
2025-09-15 09:31:47 +00:00
},
},
{
2025-09-15 19:46:52 +00:00
key: 'grossSalary',
header: 'Brüt Maaş',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => (
2025-09-15 19:46:52 +00:00
<div className="font-medium text-gray-900">{payroll.grossSalary.toLocaleString()}</div>
2025-09-15 09:31:47 +00:00
),
},
{
2025-09-15 19:46:52 +00:00
key: 'netSalary',
header: 'Net Maaş',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => (
2025-09-15 19:46:52 +00:00
<div className="font-bold text-green-600">{payroll.netSalary.toLocaleString()}</div>
2025-09-15 09:31:47 +00:00
),
},
{
2025-09-15 19:46:52 +00:00
key: 'status',
header: 'Durum',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getPayrollStatusColor(
2025-09-15 19:46:52 +00:00
payroll.status,
2025-09-15 09:31:47 +00:00
)}`}
>
{getPayrollStatusText(payroll.status)}
</span>
),
},
{
2025-09-15 19:46:52 +00:00
key: 'actions',
header: 'İşlemler',
2025-09-15 09:31:47 +00:00
render: (payroll: HrPayroll) => (
<div className="flex gap-1">
<button
onClick={() => handleView(payroll)}
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
{payroll.status === PayrollStatusEnum.Draft && (
<button
onClick={() => handleCalculate(payroll)}
className="p-1 text-green-600 hover:bg-green-50 rounded"
title="Hesapla"
>
<FaCalculator className="w-4 h-4" />
</button>
)}
{payroll.status === PayrollStatusEnum.Calculated && (
<>
<button
onClick={() => handleApprove(payroll)}
className="p-1 text-green-600 hover:bg-green-50 rounded"
title="Onayla"
>
<FaCheck className="w-4 h-4" />
</button>
<button
onClick={() => handleReject(payroll)}
className="p-1 text-red-600 hover:bg-red-50 rounded"
title="Reddet"
>
<FaBan className="w-4 h-4" />
</button>
</>
)}
<button
onClick={() => handleEdit(payroll)}
className="p-1 text-gray-600 hover:bg-gray-50 rounded"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
{(payroll.status === PayrollStatusEnum.Approved ||
payroll.status === PayrollStatusEnum.Paid) && (
<button
onClick={() => handleExport(payroll)}
className="p-1 text-purple-600 hover:bg-purple-50 rounded"
title="Dışa Aktar"
>
<FaDownload className="w-4 h-4" />
</button>
)}
</div>
),
},
2025-09-15 19:46:52 +00:00
]
2025-09-15 09:31:47 +00:00
// 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(
2025-09-15 19:46:52 +00:00
(p) => p.status === PayrollStatusEnum.Draft || p.status === PayrollStatusEnum.Calculated,
2025-09-15 09:31:47 +00:00
).length,
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
// Get unique periods for filter
2025-09-15 19:46:52 +00:00
const periods = [...new Set(payrolls.map((p) => p.period))].sort().reverse()
2025-09-15 09:31:47 +00:00
return (
2025-09-15 19:46:52 +00:00
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
2025-09-15 21:02:48 +00:00
<h2 className="text-2xl font-bold text-gray-900">Maaş, Prim, Bordro Yönetimi</h2>
<p className="text-gray-600">Personel ödemelerini hesaplayın ve yönetin</p>
2025-09-15 19:46:52 +00:00
</div>
<div className="flex gap-2">
<button
onClick={handleBulkEntry}
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors"
>
<FaUsers className="w-4 h-4" />
Toplu Bordro Girişi
</button>
<button
onClick={handleAdd}
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<FaPlus className="w-4 h-4" />
Yeni Bordro
</button>
</div>
2025-09-15 09:31:47 +00:00
</div>
2025-09-15 19:46:52 +00:00
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-5 gap-6">
<Widget title="Toplam Bordro" value={stats.total} color="blue" icon="FaFileAlt" />
<Widget
title="Toplam Brüt"
value={`${stats.totalGross.toLocaleString()}`}
color="green"
icon="FaDollarSign"
/>
<Widget
title="Toplam Net"
value={`${stats.totalNet.toLocaleString()}`}
color="purple"
icon="FaDollarSign"
/>
<Widget
title="Toplam Vergi"
value={`${stats.totalTax.toLocaleString()}`}
color="red"
icon="FaCalculator"
/>
<Widget title="Beklemede" value={stats.pending} color="yellow" icon="FaFileAlt" />
</div>
{/* Filters */}
<div className="flex gap-4 items-center">
<select
value={selectedDepartment}
onChange={(e) => setSelectedDepartment(e.target.value)}
className="px-3 py-1.5 border border-gray-300 text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
2025-09-15 09:31:47 +00:00
>
2025-09-15 19:46:52 +00:00
<option value="all">Tüm Departmanlar</option>
{mockDepartments.map((dept) => (
<option key={dept.id} value={dept.name}>
{dept.name}
</option>
))}
</select>
<select
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-3 py-1.5 border border-gray-300 text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
2025-09-15 09:31:47 +00:00
>
2025-09-15 19:46:52 +00:00
<option value="">Personel seçiniz...</option>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.id}>
{employee.fullName} ({employee.code})
</option>
))}
</select>
<select
value={selectedStatus}
onChange={(e) => setSelectedStatus(e.target.value)}
className="px-3 py-1.5 border border-gray-300 text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tüm Durumlar</option>
2025-09-17 09:46:58 +00:00
{Object.values(PayrollStatusEnum).map((status) => (
<option key={status} value={status}>
{getPayrollStatusText(status)}
</option>
))}
2025-09-15 19:46:52 +00:00
</select>
<select
value={selectedPeriod}
onChange={(e) => setSelectedPeriod(e.target.value)}
className="px-3 py-1.5 border border-gray-300 text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tüm Dönemler</option>
{periods.map((period) => (
<option key={period} value={period}>
{period}
</option>
))}
</select>
2025-09-15 09:31:47 +00:00
</div>
2025-09-15 19:46:52 +00:00
{/* Data Table */}
<div className="bg-white rounded-lg shadow-sm border">
<DataTable data={filteredPayrolls} columns={columns} />
</div>
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
{filteredPayrolls.length === 0 && (
<div className="text-center py-12">
<FaFileAlt className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Bordro bulunamadı</h3>
<p className="text-gray-500">Seçilen kriterlere uygun bordro bulunmamaktadır.</p>
</div>
)}
2025-09-15 09:31:47 +00:00
</div>
{/* Modal */}
{showModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-medium text-gray-900">
2025-09-15 19:46:52 +00:00
{modalType === 'add' && 'Yeni Bordro Ekle'}
{modalType === 'edit' && 'Bordro Düzenle'}
{modalType === 'view' && 'Bordro Detayları'}
2025-09-15 09:31:47 +00:00
</h3>
2025-09-15 19:46:52 +00:00
<button onClick={handleCancel} className="text-gray-400 hover:text-gray-600">
2025-09-15 09:31:47 +00:00
<FaTimes className="w-5 h-5" />
</button>
</div>
<div className="space-y-4">
{/* Employee Selection */}
2025-09-15 19:46:52 +00:00
{modalType === 'add' && (
2025-09-15 09:31:47 +00:00
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Personel</label>
2025-09-15 09:31:47 +00:00
<select
2025-09-15 19:46:52 +00:00
value={formData.employeeId || ''}
onChange={(e) => setFormData({ ...formData, employeeId: e.target.value })}
2025-09-15 09:31:47 +00:00
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Personel Seçiniz</option>
{mockEmployees.map((emp) => (
<option key={emp.id} value={emp.id}>
{emp.code} - {emp.fullName}
</option>
))}
</select>
</div>
)}
{/* Employee Info (for edit/view) */}
2025-09-15 19:46:52 +00:00
{(modalType === 'edit' || modalType === 'view') && formData.employee && (
<div className="bg-gray-50 p-3 rounded-md">
<div className="text-sm text-gray-600">Personel</div>
<div className="font-medium">
{formData.employee.code} - {formData.employee.fullName}
2025-09-15 09:31:47 +00:00
</div>
2025-09-15 19:46:52 +00:00
</div>
)}
2025-09-15 09:31:47 +00:00
{/* Period */}
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Dönem</label>
2025-09-15 09:31:47 +00:00
<input
type="month"
value={formData.period}
2025-09-15 19:46:52 +00:00
onChange={(e) => setFormData({ ...formData, period: e.target.value })}
2025-09-15 09:31:47 +00:00
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
2025-09-15 19:46:52 +00:00
disabled={modalType === 'view'}
2025-09-15 09:31:47 +00:00
/>
</div>
{/* Base Salary */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Temel Maaş ()
</label>
<input
type="number"
value={formData.baseSalary}
onChange={(e) =>
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"
2025-09-15 19:46:52 +00:00
disabled={modalType === 'view'}
2025-09-15 09:31:47 +00:00
/>
</div>
{/* Overtime */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Mesai Ücreti ()
</label>
<input
type="number"
value={formData.overtime}
onChange={(e) =>
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"
2025-09-15 19:46:52 +00:00
disabled={modalType === 'view'}
2025-09-15 09:31:47 +00:00
/>
</div>
{/* Bonus */}
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Prim ()</label>
2025-09-15 09:31:47 +00:00
<input
type="number"
value={formData.bonus}
2025-09-15 19:46:52 +00:00
onChange={(e) => setFormData({ ...formData, bonus: Number(e.target.value) })}
2025-09-15 09:31:47 +00:00
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
2025-09-15 19:46:52 +00:00
disabled={modalType === 'view'}
2025-09-15 09:31:47 +00:00
/>
</div>
{/* Allowances */}
2025-09-15 19:46:52 +00:00
{modalType !== 'view' && (
2025-09-15 09:31:47 +00:00
<div>
<div className="flex items-center justify-between mb-2">
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Ödemeler</label>
2025-09-15 09:31:47 +00:00
<button
type="button"
onClick={() => {
const newAllowances = [
...(formData.allowances || []),
{
id: Date.now().toString(),
2025-09-15 19:46:52 +00:00
name: '',
2025-09-15 09:31:47 +00:00
amount: 0,
taxable: true,
},
2025-09-15 19:46:52 +00:00
]
setFormData({ ...formData, allowances: newAllowances })
2025-09-15 09:31:47 +00:00
}}
className="text-blue-600 hover:text-blue-700 text-sm"
>
+ Ödeme Ekle
</button>
</div>
{formData.allowances?.map((allowance, index) => (
<div key={allowance.id} className="flex gap-2 mb-2">
<input
type="text"
placeholder="Ödeme adı"
value={allowance.name}
onChange={(e) => {
2025-09-15 19:46:52 +00:00
const updatedAllowances = [...(formData.allowances || [])]
2025-09-15 09:31:47 +00:00
updatedAllowances[index] = {
...allowance,
name: e.target.value,
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setFormData({
...formData,
allowances: updatedAllowances,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<input
type="number"
placeholder="Tutar"
value={allowance.amount}
onChange={(e) => {
2025-09-15 19:46:52 +00:00
const updatedAllowances = [...(formData.allowances || [])]
2025-09-15 09:31:47 +00:00
updatedAllowances[index] = {
...allowance,
amount: Number(e.target.value),
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setFormData({
...formData,
allowances: updatedAllowances,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="button"
onClick={() => {
const updatedAllowances =
2025-09-15 19:46:52 +00:00
formData.allowances?.filter((_, i) => i !== index) || []
2025-09-15 09:31:47 +00:00
setFormData({
...formData,
allowances: updatedAllowances,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="px-2 py-2 text-red-600 hover:text-red-700"
>
<FaTimes className="w-4 h-4" />
</button>
</div>
))}
2025-09-15 19:46:52 +00:00
{(!formData.allowances || formData.allowances.length === 0) && (
<p className="text-sm text-gray-500">Henüz ödeme eklenmedi</p>
2025-09-15 09:31:47 +00:00
)}
</div>
)}
{/* Deductions */}
2025-09-15 19:46:52 +00:00
{modalType !== 'view' && (
2025-09-15 09:31:47 +00:00
<div>
<div className="flex items-center justify-between mb-2">
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Kesintiler</label>
2025-09-15 09:31:47 +00:00
<button
type="button"
onClick={() => {
const newDeductions = [
...(formData.deductions || []),
{
id: Date.now().toString(),
2025-09-15 19:46:52 +00:00
name: '',
2025-09-15 09:31:47 +00:00
amount: 0,
mandatory: false,
},
2025-09-15 19:46:52 +00:00
]
setFormData({ ...formData, deductions: newDeductions })
2025-09-15 09:31:47 +00:00
}}
className="text-blue-600 hover:text-blue-700 text-sm"
>
+ Kesinti Ekle
</button>
</div>
{formData.deductions?.map((deduction, index) => (
<div key={deduction.id} className="flex gap-2 mb-2">
<input
type="text"
placeholder="Kesinti adı"
value={deduction.name}
onChange={(e) => {
2025-09-15 19:46:52 +00:00
const updatedDeductions = [...(formData.deductions || [])]
2025-09-15 09:31:47 +00:00
updatedDeductions[index] = {
...deduction,
name: e.target.value,
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setFormData({
...formData,
deductions: updatedDeductions,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<input
type="number"
placeholder="Tutar"
value={deduction.amount}
onChange={(e) => {
2025-09-15 19:46:52 +00:00
const updatedDeductions = [...(formData.deductions || [])]
2025-09-15 09:31:47 +00:00
updatedDeductions[index] = {
...deduction,
amount: Number(e.target.value),
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
setFormData({
...formData,
deductions: updatedDeductions,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="button"
onClick={() => {
const updatedDeductions =
2025-09-15 19:46:52 +00:00
formData.deductions?.filter((_, i) => i !== index) || []
2025-09-15 09:31:47 +00:00
setFormData({
...formData,
deductions: updatedDeductions,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="px-2 py-2 text-red-600 hover:text-red-700"
>
<FaTimes className="w-4 h-4" />
</button>
</div>
))}
2025-09-15 19:46:52 +00:00
{(!formData.deductions || formData.deductions.length === 0) && (
<p className="text-sm text-gray-500">Henüz kesinti eklenmedi</p>
2025-09-15 09:31:47 +00:00
)}
</div>
)}
{/* Real-time calculation preview for add/edit modes */}
2025-09-15 19:46:52 +00:00
{modalType !== 'view' && (
2025-09-15 09:31:47 +00:00
<div className="bg-blue-50 p-4 rounded-lg">
2025-09-15 19:46:52 +00:00
<h5 className="font-medium text-gray-900 mb-3">Hesaplama Önizlemesi</h5>
2025-09-15 09:31:47 +00:00
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-sm text-gray-600">Brüt Maaş</div>
<div className="font-medium text-lg text-gray-900">
{calculatedValues.grossSalary.toLocaleString()}
</div>
<div className="text-xs text-gray-500">
2025-09-15 19:46:52 +00:00
Temel: {(formData.baseSalary || 0).toLocaleString()} + Ödemeler:
2025-09-15 09:31:47 +00:00
{(formData.allowances || [])
2025-09-15 19:46:52 +00:00
.reduce((total, allowance) => total + allowance.amount, 0)
.toLocaleString()}{' '}
+ Mesai: {(formData.overtime || 0).toLocaleString()} + Prim:
{(formData.bonus || 0).toLocaleString()}
2025-09-15 09:31:47 +00:00
</div>
</div>
<div>
<div className="text-sm text-gray-600">Net Maaş</div>
<div className="font-bold text-lg text-green-600">
{calculatedValues.netSalary.toLocaleString()}
</div>
<div className="text-xs text-gray-500">
2025-09-15 19:46:52 +00:00
Brüt - Vergi: {calculatedValues.tax.toLocaleString()} - SGK:
{calculatedValues.socialSecurity.toLocaleString()} - Kesintiler:
2025-09-15 09:31:47 +00:00
{(formData.deductions || [])
2025-09-15 19:46:52 +00:00
.reduce((total, deduction) => total + deduction.amount, 0)
2025-09-15 09:31:47 +00:00
.toLocaleString()}
</div>
</div>
</div>
</div>
)}
{/* View mode - show allowances and deductions */}
2025-09-15 19:46:52 +00:00
{modalType === 'view' && (
2025-09-15 09:31:47 +00:00
<>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Ödemeler</label>
2025-09-15 09:31:47 +00:00
<div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md">
{formData.allowances && formData.allowances.length > 0 ? (
<div className="space-y-1">
{formData.allowances.map((allowance) => (
2025-09-15 19:46:52 +00:00
<div key={allowance.id} className="flex justify-between text-sm">
2025-09-15 09:31:47 +00:00
<span>{allowance.name}</span>
<span>{allowance.amount.toLocaleString()}</span>
</div>
))}
<div className="border-t pt-1 font-medium flex justify-between">
<span>Toplam:</span>
<span>
{formData.allowances
2025-09-15 19:46:52 +00:00
.reduce((total, allowance) => total + allowance.amount, 0)
2025-09-15 09:31:47 +00:00
.toLocaleString()}
</span>
</div>
</div>
) : (
<span className="text-gray-500">Ödeme yok</span>
)}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kesintiler
</label>
<div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md">
{formData.deductions && formData.deductions.length > 0 ? (
<div className="space-y-1">
{formData.deductions.map((deduction) => (
2025-09-15 19:46:52 +00:00
<div key={deduction.id} className="flex justify-between text-sm">
2025-09-15 09:31:47 +00:00
<span>{deduction.name}</span>
<span>{deduction.amount.toLocaleString()}</span>
</div>
))}
<div className="border-t pt-1 font-medium flex justify-between">
<span>Toplam:</span>
<span>
{formData.deductions
2025-09-15 19:46:52 +00:00
.reduce((total, deduction) => total + deduction.amount, 0)
2025-09-15 09:31:47 +00:00
.toLocaleString()}
</span>
</div>
</div>
) : (
<span className="text-gray-500">Kesinti yok</span>
)}
</div>
</div>
</>
)}
{/* Calculated fields (show only in view/edit mode for existing payrolls) */}
2025-09-15 19:46:52 +00:00
{modalType === 'view' && (
2025-09-15 09:31:47 +00:00
<>
{/* Status */}
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Durum</label>
2025-09-15 09:31:47 +00:00
<div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md">
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getPayrollStatusColor(
2025-09-15 19:46:52 +00:00
formData.status || PayrollStatusEnum.Draft,
2025-09-15 09:31:47 +00:00
)}`}
>
2025-09-15 19:46:52 +00:00
{getPayrollStatusText(formData.status || PayrollStatusEnum.Draft)}
2025-09-15 09:31:47 +00:00
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Brüt Maaş ()
</label>
<div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md">
{formData.grossSalary?.toLocaleString()}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Net Maaş ()
</label>
<div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md font-bold text-green-600">
{formData.netSalary?.toLocaleString()}
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Vergi ()
</label>
<div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md">
{formData.tax?.toLocaleString()}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
SGK ()
</label>
<div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md">
{formData.socialSecurity?.toLocaleString()}
</div>
</div>
</div>
</>
)}
</div>
{/* Modal Actions */}
2025-09-15 19:46:52 +00:00
{modalType !== 'view' && (
2025-09-15 09:31:47 +00:00
<div className="flex justify-end gap-2 mt-6">
<button
onClick={handleCancel}
className="px-4 py-2 text-gray-600 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors"
>
İptal
</button>
<button
onClick={handleSubmit}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<FaSave className="w-4 h-4" />
2025-09-15 19:46:52 +00:00
{modalType === 'add' ? 'Ekle' : 'Güncelle'}
2025-09-15 09:31:47 +00:00
</button>
</div>
)}
2025-09-15 19:46:52 +00:00
{modalType === 'view' && (
2025-09-15 09:31:47 +00:00
<div className="flex justify-end mt-6">
<button
onClick={handleCancel}
className="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors"
>
Kapat
</button>
</div>
)}
</div>
</div>
)}
{/* Bulk Entry Modal */}
{showBulkModal && (
<BulkPayrollEntry
onClose={() => setShowBulkModal(false)}
onSubmit={(payrolls) => {
2025-09-15 19:46:52 +00:00
setPayrolls((prev) => [...prev, ...payrolls])
setShowBulkModal(false)
2025-09-15 09:31:47 +00:00
}}
/>
)}
2025-09-15 19:46:52 +00:00
</Container>
)
}
2025-09-15 09:31:47 +00:00
// Bulk Payroll Entry Component
interface BulkPayrollEntryProps {
2025-09-15 19:46:52 +00:00
onClose: () => void
onSubmit: (payrolls: HrPayroll[]) => void
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
const BulkPayrollEntry: React.FC<BulkPayrollEntryProps> = ({ onClose, onSubmit }) => {
const [selectedDepartment, setSelectedDepartment] = useState<string>('')
const [selectedEmployees, setSelectedEmployees] = useState<string[]>([])
2025-09-15 09:31:47 +00:00
const [period, setPeriod] = useState<string>(
2025-09-15 19:46:52 +00:00
new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'),
)
2025-09-15 09:31:47 +00:00
const [bulkData, setBulkData] = useState({
baseSalaryIncrease: 0,
overtimeHours: 0,
overtimeRate: 50,
bonusAmount: 0,
massBonus: 0,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
// Get unique departments from mockDepartments and employees
const departments = [
...new Set([
...mockDepartments.map((dept) => dept.name),
...mockEmployees.map((emp) => emp.department?.name).filter(Boolean),
]),
2025-09-15 19:46:52 +00:00
].sort()
2025-09-15 09:31:47 +00:00
// Get employees by department
const filteredEmployees = selectedDepartment
? mockEmployees.filter((emp) => emp.department?.name === selectedDepartment)
2025-09-15 19:46:52 +00:00
: []
2025-09-15 09:31:47 +00:00
// Get selected employees data
const selectedEmployeesData = filteredEmployees.filter((emp) =>
2025-09-15 19:46:52 +00:00
selectedEmployees.includes(emp.id),
)
2025-09-15 09:31:47 +00:00
// Handle department change - reset selected employees and auto-select all
const handleDepartmentChange = (departmentName: string) => {
2025-09-15 19:46:52 +00:00
setSelectedDepartment(departmentName)
2025-09-15 09:31:47 +00:00
// Auto-select all employees when department is selected
if (departmentName) {
2025-09-15 19:46:52 +00:00
const employeesInDept = mockEmployees.filter((emp) => emp.department?.name === departmentName)
setSelectedEmployees(employeesInDept.map((emp) => emp.id))
2025-09-15 09:31:47 +00:00
} else {
2025-09-15 19:46:52 +00:00
setSelectedEmployees([])
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
// Handle select all employees
const handleSelectAll = (checked: boolean) => {
if (checked) {
2025-09-15 19:46:52 +00:00
setSelectedEmployees(filteredEmployees.map((emp) => emp.id))
2025-09-15 09:31:47 +00:00
} else {
2025-09-15 19:46:52 +00:00
setSelectedEmployees([])
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
// Handle individual employee selection
const handleEmployeeSelect = (employeeId: string, checked: boolean) => {
if (checked) {
2025-09-15 19:46:52 +00:00
setSelectedEmployees((prev) => [...prev, employeeId])
2025-09-15 09:31:47 +00:00
} else {
2025-09-15 19:46:52 +00:00
setSelectedEmployees((prev) => prev.filter((id) => id !== employeeId))
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
const handleBulkSubmit = () => {
if (!selectedDepartment || selectedEmployeesData.length === 0) {
2025-09-15 19:46:52 +00:00
alert('Lütfen bir departman ve en az bir personel seçin')
return
2025-09-15 09:31:47 +00:00
}
const newPayrolls: HrPayroll[] = selectedEmployeesData.map((employee) => {
2025-09-15 19:46:52 +00:00
const baseSalary = employee.baseSalary + bulkData.baseSalaryIncrease
const overtime = bulkData.overtimeHours * bulkData.overtimeRate
const bonus = bulkData.bonusAmount + bulkData.massBonus
2025-09-15 09:31:47 +00:00
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(),
2025-09-15 19:46:52 +00:00
}
})
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
onSubmit(newPayrolls)
}
2025-09-15 09:31:47 +00:00
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-4">
2025-09-15 19:46:52 +00:00
<h3 className="text-lg font-medium text-gray-900">Toplu Bordro Girişi</h3>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
2025-09-15 09:31:47 +00:00
<FaTimes className="w-5 h-5" />
</button>
</div>
<div className="space-y-6">
{/* Department and Period */}
<div className="grid grid-cols-2 gap-4">
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Departman</label>
2025-09-15 09:31:47 +00:00
<select
value={selectedDepartment}
onChange={(e) => handleDepartmentChange(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"
>
<option value="">Departman Seçiniz</option>
{departments.map((dept) => (
<option key={dept} value={dept}>
{dept}
</option>
))}
</select>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Dönem</label>
2025-09-15 09:31:47 +00:00
<input
type="month"
value={period}
onChange={(e) => 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"
/>
</div>
</div>
{/* Bulk Settings */}
<div className="bg-gray-50 p-4 rounded-lg">
<h4 className="font-medium text-gray-900 mb-3">Toplu Ayarlar</h4>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Maaş Artışı ()
</label>
<input
type="number"
value={bulkData.baseSalaryIncrease}
onChange={(e) =>
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"
/>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Mesai Saati</label>
2025-09-15 09:31:47 +00:00
<input
type="number"
value={bulkData.overtimeHours}
onChange={(e) =>
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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Mesai Saatlik Ücreti ()
</label>
<input
type="number"
value={bulkData.overtimeRate}
onChange={(e) =>
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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Departman Primi ()
</label>
<input
type="number"
value={bulkData.massBonus}
onChange={(e) =>
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"
/>
</div>
</div>
</div>
{/* Employee Selection */}
{selectedDepartment && filteredEmployees.length > 0 && (
<div>
<div className="flex items-center justify-between mb-3">
<h4 className="font-medium text-gray-900">
Personel Seçimi ({filteredEmployees.length} kişi)
</h4>
<label className="flex items-center">
<input
type="checkbox"
checked={
selectedEmployees.length === filteredEmployees.length &&
filteredEmployees.length > 0
}
onChange={(e) => handleSelectAll(e.target.checked)}
className="mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<span className="text-sm text-gray-700">Tümünü Seç</span>
</label>
</div>
<div className="max-h-60 overflow-y-auto border border-gray-300 rounded-md">
<table className="min-w-full">
<thead className="bg-gray-50 sticky top-0">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase w-12">
Seç
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
Personel Kodu
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
Ad Soyad
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
Mevcut Maaş
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
Yeni Maaş
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredEmployees.map((employee) => (
<tr
key={employee.id}
2025-09-15 19:46:52 +00:00
className={selectedEmployees.includes(employee.id) ? 'bg-blue-50' : ''}
2025-09-15 09:31:47 +00:00
>
<td className="px-4 py-2">
<input
type="checkbox"
checked={selectedEmployees.includes(employee.id)}
2025-09-15 19:46:52 +00:00
onChange={(e) => handleEmployeeSelect(employee.id, e.target.checked)}
2025-09-15 09:31:47 +00:00
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
</td>
2025-09-15 19:46:52 +00:00
<td className="px-4 py-2 text-sm text-gray-900">{employee.code}</td>
<td className="px-4 py-2 text-sm text-gray-900">{employee.fullName}</td>
2025-09-15 09:31:47 +00:00
<td className="px-4 py-2 text-sm text-gray-900">
{employee.baseSalary.toLocaleString()}
</td>
<td className="px-4 py-2 text-sm font-medium text-green-600">
2025-09-15 19:46:52 +00:00
{(employee.baseSalary + bulkData.baseSalaryIncrease).toLocaleString()}
2025-09-15 09:31:47 +00:00
</td>
</tr>
))}
</tbody>
</table>
</div>
{selectedEmployees.length > 0 && (
<div className="mt-3 p-3 bg-blue-50 rounded-md">
<p className="text-sm text-blue-800">
2025-09-15 19:46:52 +00:00
<strong>{selectedEmployees.length}</strong> personel seçildi. Toplam mevcut
maaş:{' '}
2025-09-15 09:31:47 +00:00
<strong>
{selectedEmployeesData
.reduce((total, emp) => total + emp.baseSalary, 0)
.toLocaleString()}
</strong>
</p>
</div>
)}
</div>
)}
</div>
{/* Modal Actions */}
<div className="flex justify-end gap-2 mt-6">
<button
onClick={onClose}
className="px-4 py-2 text-gray-600 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors"
>
İptal
</button>
<button
onClick={handleBulkSubmit}
disabled={!selectedDepartment || selectedEmployeesData.length === 0}
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
>
<FaSave className="w-4 h-4" />
Toplu Bordro Oluştur ({selectedEmployeesData.length} kişi)
</button>
</div>
</div>
</div>
2025-09-15 19:46:52 +00:00
)
}
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
export default PayrollManagement