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

1126 lines
42 KiB
TypeScript
Raw Normal View History

2025-09-15 19:46:52 +00:00
import React, { useState } from 'react'
import { FaClock, FaUser, FaPlus, FaEdit, FaCheck, FaTimes, FaEye, FaUsers } from 'react-icons/fa'
import { LeaveStatusEnum, HrOvertime } from '../../../types/hr'
import DataTable, { Column } from '../../../components/common/DataTable'
import { mockOvertimes } from '../../../mocks/mockOvertimes'
import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockDepartments } from '../../../mocks/mockDepartments'
import Widget from '../../../components/common/Widget'
import { getLeaveStatusColor, getLeaveStatusText } from '../../../utils/erp'
import { Container } from '@/components/shared'
2025-09-15 09:31:47 +00:00
const OvertimeManagement: React.FC = () => {
// Overtime states
2025-09-15 19:46:52 +00:00
const [overtimes, setOvertimes] = useState<HrOvertime[]>(mockOvertimes)
const [overtimeSelectedStatus, setOvertimeSelectedStatus] = useState<string>('all')
const [overtimeSelectedPeriod, setOvertimeSelectedPeriod] = useState<string>('all')
const [searchOvertimesTerm, setSearchOvertimesTerm] = useState('')
const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
2025-09-15 09:31:47 +00:00
// Overtime modal states
2025-09-15 19:46:52 +00:00
const [showOvertimeAddModal, setShowOvertimeAddModal] = useState(false)
const [showOvertimeEditModal, setShowOvertimeEditModal] = useState(false)
const [showOvertimeViewModal, setShowOvertimeViewModal] = useState(false)
const [showOvertimeRejectModal, setShowOvertimeRejectModal] = useState(false)
const [showBulkOvertimeModal, setShowBulkOvertimeModal] = useState(false)
const [selectedOvertime, setSelectedOvertime] = useState<HrOvertime | null>(null)
const [overtimeRejectReason, setOvertimeRejectReason] = useState('')
2025-09-15 09:31:47 +00:00
// Overtime form state
const [overtimeFormData, setOvertimeFormData] = useState({
2025-09-15 19:46:52 +00:00
employeeId: '',
date: '',
startTime: '',
endTime: '',
reason: '',
2025-09-15 09:31:47 +00:00
rate: 1.5,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
// Bulk overtime form state
const [bulkOvertimeFormData, setBulkOvertimeFormData] = useState({
2025-09-15 19:46:52 +00:00
departmentId: '',
2025-09-15 09:31:47 +00:00
selectedEmployees: [] as string[],
2025-09-15 19:46:52 +00:00
date: '',
startTime: '',
endTime: '',
reason: '',
2025-09-15 09:31:47 +00:00
rate: 1.5,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
// Get employees by department
const getEmployeesByDepartment = (departmentId: string) => {
return mockEmployees.filter((emp) => emp.departmentId === departmentId)
2025-09-15 19:46:52 +00:00
}
2025-09-15 09:31:47 +00:00
// Get selected employee object
const getSelectedEmployee = (employeeId: string) => {
2025-09-15 19:46:52 +00:00
return mockEmployees.find((emp) => emp.id === employeeId)
}
2025-09-15 09:31:47 +00:00
// Overtime handlers
const handleOvertimeAdd = () => {
setOvertimeFormData({
2025-09-15 19:46:52 +00:00
employeeId: '',
date: '',
startTime: '',
endTime: '',
reason: '',
2025-09-15 09:31:47 +00:00
rate: 1.5,
2025-09-15 19:46:52 +00:00
})
setShowOvertimeAddModal(true)
}
2025-09-15 09:31:47 +00:00
const handleOvertimeEdit = (overtime: HrOvertime) => {
2025-09-15 19:46:52 +00:00
setSelectedOvertime(overtime)
2025-09-15 09:31:47 +00:00
setOvertimeFormData({
employeeId: overtime.employeeId,
2025-09-15 19:46:52 +00:00
date: new Date(overtime.date).toISOString().split('T')[0],
2025-09-15 09:31:47 +00:00
startTime: overtime.startTime,
endTime: overtime.endTime,
reason: overtime.reason,
rate: overtime.rate || 1.5,
2025-09-15 19:46:52 +00:00
})
setShowOvertimeEditModal(true)
}
2025-09-15 09:31:47 +00:00
const handleOvertimeApprove = (id: string) => {
setOvertimes((prevOvertimes) =>
prevOvertimes.map((overtime) =>
overtime.id === id
? {
...overtime,
status: LeaveStatusEnum.Approved,
lastModificationTime: new Date(),
}
2025-09-15 19:46:52 +00:00
: overtime,
),
)
alert('Mesai talebi onaylandı!')
}
2025-09-15 09:31:47 +00:00
const handleOvertimeReject = (id: string, reason?: string) => {
if (reason) {
setOvertimes((prevOvertimes) =>
prevOvertimes.map((overtime) =>
overtime.id === id
? {
...overtime,
status: LeaveStatusEnum.Rejected,
lastModificationTime: new Date(),
}
2025-09-15 19:46:52 +00:00
: overtime,
),
)
setShowOvertimeRejectModal(false)
setOvertimeRejectReason('')
alert('Mesai talebi reddedildi!')
2025-09-15 09:31:47 +00:00
} else {
2025-09-15 19:46:52 +00:00
const overtime = overtimes.find((o) => o.id === id)
2025-09-15 09:31:47 +00:00
if (overtime) {
2025-09-15 19:46:52 +00:00
setSelectedOvertime(overtime)
setShowOvertimeRejectModal(true)
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 handleOvertimeView = (overtime: HrOvertime) => {
2025-09-15 19:46:52 +00:00
setSelectedOvertime(overtime)
setShowOvertimeViewModal(true)
}
2025-09-15 09:31:47 +00:00
const handleSubmitOvertimeAdd = () => {
if (
!overtimeFormData.employeeId ||
!overtimeFormData.date ||
!overtimeFormData.startTime ||
!overtimeFormData.endTime
) {
2025-09-15 19:46:52 +00:00
alert('Lütfen tüm gerekli alanları doldurun!')
return
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
const startTime = new Date(`2000-01-01 ${overtimeFormData.startTime}`)
const endTime = new Date(`2000-01-01 ${overtimeFormData.endTime}`)
const totalHours = (endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60)
2025-09-15 09:31:47 +00:00
if (totalHours <= 0) {
2025-09-15 19:46:52 +00:00
alert('Bitiş saati başlangıç saatinden sonra olmalıdır!')
return
2025-09-15 09:31:47 +00:00
}
const newOvertime: HrOvertime = {
id: `ot_${Date.now()}`,
employeeId: overtimeFormData.employeeId,
date: new Date(overtimeFormData.date),
startTime: overtimeFormData.startTime,
endTime: overtimeFormData.endTime,
totalHours: totalHours,
reason: overtimeFormData.reason,
status: LeaveStatusEnum.Pending,
rate: overtimeFormData.rate,
amount: totalHours * 100 * overtimeFormData.rate, // Assuming 100 TL/hour base rate
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
setOvertimes((prevOvertimes) => [...prevOvertimes, newOvertime])
setShowOvertimeAddModal(false)
alert('Mesai talebi başarıyla oluşturuldu!')
}
2025-09-15 09:31:47 +00:00
const handleSubmitOvertimeEdit = () => {
2025-09-15 19:46:52 +00:00
if (!selectedOvertime) return
2025-09-15 09:31:47 +00:00
if (
!overtimeFormData.employeeId ||
!overtimeFormData.date ||
!overtimeFormData.startTime ||
!overtimeFormData.endTime
) {
2025-09-15 19:46:52 +00:00
alert('Lütfen tüm gerekli alanları doldurun!')
return
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
const startTime = new Date(`2000-01-01 ${overtimeFormData.startTime}`)
const endTime = new Date(`2000-01-01 ${overtimeFormData.endTime}`)
const totalHours = (endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60)
2025-09-15 09:31:47 +00:00
if (totalHours <= 0) {
2025-09-15 19:46:52 +00:00
alert('Bitiş saati başlangıç saatinden sonra olmalıdır!')
return
2025-09-15 09:31:47 +00:00
}
setOvertimes((prevOvertimes) =>
prevOvertimes.map((overtime) =>
overtime.id === selectedOvertime.id
? {
...overtime,
employeeId: overtimeFormData.employeeId,
date: new Date(overtimeFormData.date),
startTime: overtimeFormData.startTime,
endTime: overtimeFormData.endTime,
totalHours: totalHours,
reason: overtimeFormData.reason,
rate: overtimeFormData.rate,
amount: totalHours * 100 * overtimeFormData.rate,
lastModificationTime: new Date(),
}
2025-09-15 19:46:52 +00:00
: overtime,
),
)
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
setShowOvertimeEditModal(false)
setSelectedOvertime(null)
alert('Mesai talebi başarıyla güncellendi!')
}
2025-09-15 09:31:47 +00:00
const handleSubmitOvertimeReject = () => {
if (!selectedOvertime || !overtimeRejectReason.trim()) {
2025-09-15 19:46:52 +00:00
alert('Lütfen red nedeni giriniz!')
return
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
handleOvertimeReject(selectedOvertime.id, overtimeRejectReason)
}
2025-09-15 09:31:47 +00:00
const handleBulkOvertimeAdd = () => {
setBulkOvertimeFormData({
2025-09-15 19:46:52 +00:00
departmentId: '',
2025-09-15 09:31:47 +00:00
selectedEmployees: [],
2025-09-15 19:46:52 +00:00
date: '',
startTime: '',
endTime: '',
reason: '',
2025-09-15 09:31:47 +00:00
rate: 1.5,
2025-09-15 19:46:52 +00:00
})
setShowBulkOvertimeModal(true)
}
2025-09-15 09:31:47 +00:00
const handleSubmitBulkOvertime = () => {
if (
!bulkOvertimeFormData.selectedEmployees.length ||
!bulkOvertimeFormData.date ||
!bulkOvertimeFormData.startTime ||
!bulkOvertimeFormData.endTime
) {
2025-09-15 19:46:52 +00:00
alert('Lütfen en az bir personel seçin ve tüm gerekli alanları doldurun!')
return
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
const startTime = new Date(`2000-01-01 ${bulkOvertimeFormData.startTime}`)
const endTime = new Date(`2000-01-01 ${bulkOvertimeFormData.endTime}`)
const totalHours = (endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60)
2025-09-15 09:31:47 +00:00
if (totalHours <= 0) {
2025-09-15 19:46:52 +00:00
alert('Bitiş saati başlangıç saatinden sonra olmalıdır!')
return
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
const newOvertimes: HrOvertime[] = bulkOvertimeFormData.selectedEmployees.map((employeeId) => {
const employee = getSelectedEmployee(employeeId)
return {
id: `ot_${Date.now()}_${employeeId}`,
employeeId: employeeId,
employee: employee,
date: new Date(bulkOvertimeFormData.date),
startTime: bulkOvertimeFormData.startTime,
endTime: bulkOvertimeFormData.endTime,
totalHours: totalHours,
reason: bulkOvertimeFormData.reason,
status: LeaveStatusEnum.Pending,
rate: bulkOvertimeFormData.rate,
amount: totalHours * 100 * bulkOvertimeFormData.rate, // 100 TL/hour base rate
creationTime: new Date(),
lastModificationTime: new Date(),
}
})
setOvertimes((prevOvertimes) => [...prevOvertimes, ...newOvertimes])
setShowBulkOvertimeModal(false)
2025-09-15 09:31:47 +00:00
setBulkOvertimeFormData({
2025-09-15 19:46:52 +00:00
departmentId: '',
2025-09-15 09:31:47 +00:00
selectedEmployees: [],
2025-09-15 19:46:52 +00:00
date: '',
startTime: '',
endTime: '',
reason: '',
2025-09-15 09:31:47 +00:00
rate: 1.5,
2025-09-15 19:46:52 +00:00
})
alert(`${newOvertimes.length} personel için toplu mesai talebi başarıyla oluşturuldu!`)
}
2025-09-15 09:31:47 +00:00
// Filter overtime data
const filteredOvertimes = overtimes.filter((overtime) => {
2025-09-15 19:46:52 +00:00
if (overtimeSelectedStatus !== 'all' && overtime.status !== overtimeSelectedStatus) {
return false
2025-09-15 09:31:47 +00:00
}
if (
2025-09-15 19:46:52 +00:00
selectedDepartment !== 'all' &&
2025-09-15 09:31:47 +00:00
overtime.employee?.department?.name !== selectedDepartment
) {
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
if (overtimeSelectedPeriod !== 'all') {
const now = new Date()
const overtimeDate = new Date(overtime.date)
2025-09-15 09:31:47 +00:00
switch (overtimeSelectedPeriod) {
2025-09-15 19:46:52 +00:00
case 'this-month': {
const currentMonth = now.getMonth()
const currentYear = now.getFullYear()
2025-09-15 09:31:47 +00:00
return (
2025-09-15 19:46:52 +00:00
overtimeDate.getMonth() === currentMonth && overtimeDate.getFullYear() === currentYear
)
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
case 'last-month': {
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1)
2025-09-15 09:31:47 +00:00
return (
overtimeDate.getMonth() === lastMonth.getMonth() &&
overtimeDate.getFullYear() === lastMonth.getFullYear()
2025-09-15 19:46:52 +00:00
)
2025-09-15 09:31:47 +00:00
}
2025-09-15 19:46:52 +00:00
case 'last-3-months': {
const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 3)
return overtimeDate >= threeMonthsAgo
2025-09-15 09:31:47 +00:00
}
default:
2025-09-15 19:46:52 +00:00
return true
2025-09-15 09:31:47 +00:00
}
}
// Search filter
2025-09-15 19:46:52 +00:00
if (searchOvertimesTerm.trim() !== '') {
2025-09-15 09:31:47 +00:00
if (overtime.employeeId !== searchOvertimesTerm) {
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
// Overtime table columns
const overtimeColumns: Column<HrOvertime>[] = [
{
2025-09-15 19:46:52 +00:00
key: 'employee',
header: 'Personel',
2025-09-15 09:31:47 +00:00
render: (overtime: HrOvertime) => {
2025-09-15 19:46:52 +00:00
const employee = mockEmployees.find((emp) => emp.id === overtime.employeeId)
2025-09-15 09:31:47 +00:00
return (
<div className="flex items-center gap-2">
<FaUser className="w-4 h-4 text-gray-500" />
<div>
2025-09-15 19:46:52 +00:00
<div className="font-medium text-gray-900">{employee?.fullName}</div>
2025-09-15 09:31:47 +00:00
<div className="text-sm text-gray-500">{employee?.code}</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
key: 'date',
header: 'Tarih',
2025-09-15 09:31:47 +00:00
render: (overtime: HrOvertime) => (
2025-09-15 19:46:52 +00:00
<div className="text-sm">{new Date(overtime.date).toLocaleDateString('tr-TR')}</div>
2025-09-15 09:31:47 +00:00
),
},
{
2025-09-15 19:46:52 +00:00
key: 'time',
header: 'Mesai Saatleri',
2025-09-15 09:31:47 +00:00
render: (overtime: HrOvertime) => (
<div className="text-sm">
<div>
{overtime.startTime} - {overtime.endTime}
</div>
<div className="text-gray-500">{overtime.totalHours} saat</div>
</div>
),
},
{
2025-09-15 19:46:52 +00:00
key: 'reason',
header: 'Neden',
2025-09-15 09:31:47 +00:00
render: (overtime: HrOvertime) => (
<div className="text-sm max-w-xs truncate" title={overtime.reason}>
{overtime.reason}
</div>
),
},
{
2025-09-15 19:46:52 +00:00
key: 'rate',
header: 'Çarpan',
render: (overtime: HrOvertime) => <div className="text-sm">x{overtime.rate}</div>,
2025-09-15 09:31:47 +00:00
},
{
2025-09-15 19:46:52 +00:00
key: 'amount',
header: 'Tutar',
2025-09-15 09:31:47 +00:00
render: (overtime: HrOvertime) => (
2025-09-15 19:46:52 +00:00
<div className="text-sm font-medium">{overtime.amount?.toLocaleString('tr-TR')} </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: (overtime: HrOvertime) => (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getLeaveStatusColor(
2025-09-15 19:46:52 +00:00
overtime.status,
2025-09-15 09:31:47 +00:00
)}`}
>
{getLeaveStatusText(overtime.status)}
</span>
),
},
{
2025-09-15 19:46:52 +00:00
key: 'actions',
header: 'İşlemler',
2025-09-15 09:31:47 +00:00
render: (overtime: HrOvertime) => (
<div className="flex gap-1">
<button
onClick={() => handleOvertimeView(overtime)}
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
{overtime.status === LeaveStatusEnum.Pending && (
<>
<button
onClick={() => handleOvertimeApprove(overtime.id)}
className="p-1 text-green-600 hover:bg-green-50 rounded"
title="Onayla"
>
<FaCheck className="w-4 h-4" />
</button>
<button
onClick={() => handleOvertimeReject(overtime.id)}
className="p-1 text-red-600 hover:bg-red-50 rounded"
title="Reddet"
>
<FaTimes className="w-4 h-4" />
</button>
</>
)}
<button
onClick={() => handleOvertimeEdit(overtime)}
className="p-1 text-gray-600 hover:bg-gray-50 rounded"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
</div>
),
},
2025-09-15 19:46:52 +00:00
]
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">Mesai Yönetimi</h2>
<p className="text-gray-600">Personel mesai talepleri ve onay süreçleri</p>
2025-09-15 19:46:52 +00:00
</div>
<div className="flex gap-2">
<button
onClick={handleOvertimeAdd}
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 Mesai Talebi
</button>
<button
onClick={handleBulkOvertimeAdd}
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 Mesai
</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-4 gap-4">
<Widget title="Toplam Mesai" value={overtimes.length} color="blue" icon="FaClock" />
<Widget
title="Beklemede"
value={overtimes.filter((o) => o.status === LeaveStatusEnum.Pending).length}
color="yellow"
icon="FaClock"
/>
<Widget
title="Onaylanan"
value={overtimes.filter((o) => o.status === LeaveStatusEnum.Approved).length}
color="green"
icon="FaCheck"
/>
<Widget
title="Toplam Tutar"
value={`${overtimes
.filter((o) => o.status === LeaveStatusEnum.Approved)
.reduce((sum, o) => sum + (o.amount || 0), 0)
.toLocaleString('tr-TR')} `}
color="purple"
icon="FaDollarSign"
/>
</div>
{/* Filters */}
<div className="flex gap-3 items-center">
<select
value={selectedDepartment}
onChange={(e) => setSelectedDepartment(e.target.value)}
className="px-3 py-1.5 border border-gray-300 rounded-md text-sm 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={searchOvertimesTerm}
onChange={(e) => setSearchOvertimesTerm(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={overtimeSelectedStatus}
onChange={(e) => setOvertimeSelectedStatus(e.target.value)}
className="border border-gray-300 rounded-lg px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="all">Tüm Durumlar</option>
2025-09-17 09:46:58 +00:00
{Object.values(LeaveStatusEnum).map((status) => (
<option key={status} value={status}>
{getLeaveStatusText(status)}
</option>
))}
2025-09-15 19:46:52 +00:00
</select>
<select
value={overtimeSelectedPeriod}
onChange={(e) => setOvertimeSelectedPeriod(e.target.value)}
className="border border-gray-300 rounded-lg px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="all">Tüm Dönemler</option>
<option value="this-month">Bu Ay</option>
<option value="last-month">Geçen Ay</option>
<option value="last-3-months">Son 3 Ay</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={filteredOvertimes} columns={overtimeColumns} />
</div>
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
{filteredOvertimes.length === 0 && (
<div className="text-center py-12">
<FaClock className="w-10 h-10 text-gray-400 mx-auto mb-3" />
<h3 className="text-base font-medium text-gray-900 mb-2">Mesai talebi bulunamadı</h3>
<p className="text-gray-500">Seçilen kriterlere uygun mesai talebi bulunmamaktadır.</p>
</div>
)}
2025-09-15 09:31:47 +00:00
</div>
{/* Overtime Add/Edit Modal */}
2025-09-15 19:46:52 +00:00
{(showOvertimeAddModal || (showOvertimeEditModal && selectedOvertime)) && (
2025-09-15 09:31:47 +00:00
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-full max-w-md">
<h3 className="text-base font-semibold mb-4">
2025-09-15 19:46:52 +00:00
{showOvertimeAddModal ? 'Yeni Mesai Talebi' : 'Mesai Talebini Düzenle'}
2025-09-15 09:31:47 +00:00
</h3>
<div className="space-y-4">
<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
value={overtimeFormData.employeeId}
onChange={(e) =>
setOvertimeFormData({
...overtimeFormData,
employeeId: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Personel seçiniz...</option>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.id}>
{employee.fullName} ({employee.code})
</option>
))}
</select>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Tarih</label>
2025-09-15 09:31:47 +00:00
<input
type="date"
value={overtimeFormData.date}
onChange={(e) =>
setOvertimeFormData({
...overtimeFormData,
date: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Başlangıç Saati
</label>
<input
type="time"
value={overtimeFormData.startTime}
onChange={(e) =>
setOvertimeFormData({
...overtimeFormData,
startTime: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm 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">
Bitiş Saati
</label>
<input
type="time"
value={overtimeFormData.endTime}
onChange={(e) =>
setOvertimeFormData({
...overtimeFormData,
endTime: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Mesai Çarpanı
</label>
<select
value={overtimeFormData.rate}
onChange={(e) =>
setOvertimeFormData({
...overtimeFormData,
rate: parseFloat(e.target.value),
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value={1.5}>x1.5 (Normal Mesai)</option>
<option value={2.0}>x2.0 (Hafta Sonu/Tatil)</option>
<option value={2.5}>x2.5 (Resmi Tatil)</option>
</select>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Neden</label>
2025-09-15 09:31:47 +00:00
<textarea
value={overtimeFormData.reason}
onChange={(e) =>
setOvertimeFormData({
...overtimeFormData,
reason: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Mesai nedenini belirtiniz..."
/>
</div>
</div>
<div className="flex justify-end gap-2 mt-6">
<button
onClick={() => {
2025-09-15 19:46:52 +00:00
setShowOvertimeAddModal(false)
setShowOvertimeEditModal(false)
setSelectedOvertime(null)
2025-09-15 09:31:47 +00:00
}}
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50"
>
İptal
</button>
<button
2025-09-15 19:46:52 +00:00
onClick={showOvertimeAddModal ? handleSubmitOvertimeAdd : handleSubmitOvertimeEdit}
2025-09-15 09:31:47 +00:00
className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
2025-09-15 19:46:52 +00:00
{showOvertimeAddModal ? 'Oluştur' : 'Güncelle'}
2025-09-15 09:31:47 +00:00
</button>
</div>
</div>
</div>
)}
{/* Overtime View Modal */}
{showOvertimeViewModal && selectedOvertime && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-full max-w-lg">
<h3 className="text-base font-semibold mb-4">Mesai Detayları</h3>
<div className="space-y-4">
<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">Personel</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900">
2025-09-15 19:46:52 +00:00
{mockEmployees.find((emp) => emp.id === selectedOvertime.employeeId)
?.fullName || selectedOvertime.employeeId}
2025-09-15 09:31:47 +00:00
</p>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Tarih</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900">
2025-09-15 19:46:52 +00:00
{new Date(selectedOvertime.date).toLocaleDateString('tr-TR')}
2025-09-15 09:31:47 +00:00
</p>
</div>
</div>
<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">Başlangıç Saati</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900">{selectedOvertime.startTime}</p>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Bitiş Saati</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900">{selectedOvertime.endTime}</p>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Toplam Saat</label>
<p className="text-gray-900">{selectedOvertime.totalHours} saat</p>
2025-09-15 09:31:47 +00:00
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Çarpan</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900">x{selectedOvertime.rate}</p>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Tutar</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900 font-medium">
2025-09-15 19:46:52 +00:00
{selectedOvertime.amount?.toLocaleString('tr-TR')}
2025-09-15 09:31:47 +00:00
</p>
</div>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Durum</label>
2025-09-15 09:31:47 +00:00
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getLeaveStatusColor(
2025-09-15 19:46:52 +00:00
selectedOvertime.status,
2025-09-15 09:31:47 +00:00
)}`}
>
{getLeaveStatusText(selectedOvertime.status)}
</span>
</div>
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Neden</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900">{selectedOvertime.reason}</p>
</div>
{selectedOvertime.approvedBy && (
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700">Onaylayan</label>
2025-09-15 09:31:47 +00:00
<p className="text-gray-900">
2025-09-15 19:46:52 +00:00
{mockEmployees.find((emp) => emp.id === selectedOvertime.approvedBy)?.fullName}
2025-09-15 09:31:47 +00:00
</p>
</div>
)}
</div>
<div className="flex justify-end mt-6">
<button
onClick={() => {
2025-09-15 19:46:52 +00:00
setShowOvertimeViewModal(false)
setSelectedOvertime(null)
2025-09-15 09:31:47 +00:00
}}
className="px-3 py-1.5 text-sm bg-gray-600 text-white rounded-md hover:bg-gray-700"
>
Kapat
</button>
</div>
</div>
</div>
)}
{/* Overtime Reject Modal */}
{showOvertimeRejectModal && selectedOvertime && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-full max-w-md">
2025-09-15 19:46:52 +00:00
<h3 className="text-base font-semibold mb-4">Mesai Talebini Reddet</h3>
2025-09-15 09:31:47 +00:00
<div className="mb-4">
<p className="text-gray-700 mb-2">
<strong>
2025-09-15 19:46:52 +00:00
{mockEmployees.find((emp) => emp.id === selectedOvertime.employeeId)?.fullName ||
selectedOvertime.employeeId}
</strong>{' '}
2025-09-15 09:31:47 +00:00
adlı personelin mesai talebini reddetmek üzeresiniz.
</p>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Red Nedeni <span className="text-red-500">*</span>
</label>
<textarea
value={overtimeRejectReason}
onChange={(e) => setOvertimeRejectReason(e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Reddedilme nedenini belirtiniz..."
/>
</div>
<div className="flex justify-end gap-2">
<button
onClick={() => {
2025-09-15 19:46:52 +00:00
setShowOvertimeRejectModal(false)
setSelectedOvertime(null)
setOvertimeRejectReason('')
2025-09-15 09:31:47 +00:00
}}
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50"
>
İptal
</button>
<button
onClick={handleSubmitOvertimeReject}
className="px-3 py-1.5 text-sm bg-red-600 text-white rounded-md hover:bg-red-700"
>
Reddet
</button>
</div>
</div>
</div>
)}
{/* Bulk Overtime Modal */}
{showBulkOvertimeModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<h3 className="text-base font-semibold mb-4">Toplu Mesai Talebi</h3>
<div className="space-y-4">
{/* Department Selection */}
<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={bulkOvertimeFormData.departmentId}
onChange={(e) => {
setBulkOvertimeFormData({
...bulkOvertimeFormData,
departmentId: e.target.value,
selectedEmployees: [],
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Departman seçiniz...</option>
{mockDepartments.map((department) => (
<option key={department.id} value={department.id}>
{department.name} ({department.code})
</option>
))}
</select>
</div>
{/* Employee Selection */}
{bulkOvertimeFormData.departmentId && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Personeller
</label>
<div className="border border-gray-300 rounded-md p-3 max-h-40 overflow-y-auto">
<div className="space-y-2">
<label className="flex items-center">
<input
type="checkbox"
checked={
2025-09-15 19:46:52 +00:00
getEmployeesByDepartment(bulkOvertimeFormData.departmentId).length >
0 &&
2025-09-15 09:31:47 +00:00
bulkOvertimeFormData.selectedEmployees.length ===
2025-09-15 19:46:52 +00:00
getEmployeesByDepartment(bulkOvertimeFormData.departmentId).length
2025-09-15 09:31:47 +00:00
}
onChange={(e) => {
2025-09-15 19:46:52 +00:00
const departmentEmployees = getEmployeesByDepartment(
bulkOvertimeFormData.departmentId,
)
2025-09-15 09:31:47 +00:00
setBulkOvertimeFormData({
...bulkOvertimeFormData,
selectedEmployees: e.target.checked
? departmentEmployees.map((emp) => emp.id)
: [],
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="mr-2"
/>
<span className="font-medium">Tümünü Seç</span>
</label>
2025-09-15 19:46:52 +00:00
{getEmployeesByDepartment(bulkOvertimeFormData.departmentId).map(
(employee) => (
<label key={employee.id} className="flex items-center">
<input
type="checkbox"
checked={bulkOvertimeFormData.selectedEmployees.includes(employee.id)}
onChange={(e) => {
setBulkOvertimeFormData({
...bulkOvertimeFormData,
selectedEmployees: e.target.checked
? [...bulkOvertimeFormData.selectedEmployees, employee.id]
: bulkOvertimeFormData.selectedEmployees.filter(
(id) => id !== employee.id,
),
})
}}
className="mr-2"
/>
<span>
{employee.fullName} ({employee.code})
</span>
</label>
),
)}
2025-09-15 09:31:47 +00:00
</div>
</div>
<p className="text-sm text-gray-500 mt-1">
2025-09-15 19:46:52 +00:00
{bulkOvertimeFormData.selectedEmployees.length} personel seçildi
2025-09-15 09:31:47 +00:00
</p>
</div>
)}
{/* Date */}
<div>
2025-09-15 19:46:52 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Mesai Tarihi</label>
2025-09-15 09:31:47 +00:00
<input
type="date"
value={bulkOvertimeFormData.date}
onChange={(e) =>
setBulkOvertimeFormData({
...bulkOvertimeFormData,
date: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{/* Time Range */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Başlangıç Saati
</label>
<input
type="time"
value={bulkOvertimeFormData.startTime}
onChange={(e) =>
setBulkOvertimeFormData({
...bulkOvertimeFormData,
startTime: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm 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">
Bitiş Saati
</label>
<input
type="time"
value={bulkOvertimeFormData.endTime}
onChange={(e) =>
setBulkOvertimeFormData({
...bulkOvertimeFormData,
endTime: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
{/* Rate */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Mesai Çarpanı
</label>
<select
value={bulkOvertimeFormData.rate}
onChange={(e) =>
setBulkOvertimeFormData({
...bulkOvertimeFormData,
rate: parseFloat(e.target.value),
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value={1.5}>x1.5 (Normal Mesai)</option>
<option value={2.0}>x2.0 (Hafta Sonu/Tatil)</option>
<option value={2.5}>x2.5 (Resmi Tatil)</option>
</select>
</div>
{/* Reason */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Neden (Opsiyonel)
</label>
<textarea
value={bulkOvertimeFormData.reason}
onChange={(e) =>
setBulkOvertimeFormData({
...bulkOvertimeFormData,
reason: e.target.value,
})
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Mesai nedenini belirtiniz..."
/>
</div>
{/* Summary */}
{bulkOvertimeFormData.selectedEmployees.length > 0 &&
bulkOvertimeFormData.startTime &&
bulkOvertimeFormData.endTime && (
<div className="bg-blue-50 p-3 rounded-lg">
2025-09-15 19:46:52 +00:00
<h4 className="text-sm font-medium text-blue-900 mb-2">Özet</h4>
2025-09-15 09:31:47 +00:00
<div className="text-sm text-blue-800 space-y-1">
<p>
2025-09-15 19:46:52 +00:00
<span className="font-medium">Personel Sayısı:</span>{' '}
2025-09-15 09:31:47 +00:00
{bulkOvertimeFormData.selectedEmployees.length}
</p>
{(() => {
2025-09-15 19:46:52 +00:00
const startTime = new Date(`2000-01-01 ${bulkOvertimeFormData.startTime}`)
const endTime = new Date(`2000-01-01 ${bulkOvertimeFormData.endTime}`)
2025-09-15 09:31:47 +00:00
const totalHours =
2025-09-15 19:46:52 +00:00
(endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60)
2025-09-15 09:31:47 +00:00
const totalAmount =
totalHours *
100 *
bulkOvertimeFormData.rate *
2025-09-15 19:46:52 +00:00
bulkOvertimeFormData.selectedEmployees.length
2025-09-15 09:31:47 +00:00
return totalHours > 0 ? (
<>
<p>
2025-09-15 19:46:52 +00:00
<span className="font-medium">Mesai Süresi:</span> {totalHours} saat
2025-09-15 09:31:47 +00:00
</p>
<p>
<span className="font-medium">Çarpan:</span> x
{bulkOvertimeFormData.rate}
</p>
<p>
2025-09-15 19:46:52 +00:00
<span className="font-medium">Toplam Tutar:</span>{' '}
{totalAmount.toLocaleString('tr-TR')}
2025-09-15 09:31:47 +00:00
</p>
</>
2025-09-15 19:46:52 +00:00
) : null
2025-09-15 09:31:47 +00:00
})()}
</div>
</div>
)}
</div>
<div className="flex justify-end gap-2 mt-6">
<button
onClick={() => {
2025-09-15 19:46:52 +00:00
setShowBulkOvertimeModal(false)
2025-09-15 09:31:47 +00:00
setBulkOvertimeFormData({
2025-09-15 19:46:52 +00:00
departmentId: '',
2025-09-15 09:31:47 +00:00
selectedEmployees: [],
2025-09-15 19:46:52 +00:00
date: '',
startTime: '',
endTime: '',
reason: '',
2025-09-15 09:31:47 +00:00
rate: 1.5,
2025-09-15 19:46:52 +00:00
})
2025-09-15 09:31:47 +00:00
}}
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50"
>
İptal
</button>
<button
onClick={handleSubmitBulkOvertime}
className="px-3 py-1.5 text-sm bg-green-600 text-white rounded-md hover:bg-green-700"
>
Toplu Mesai Oluştur
</button>
</div>
</div>
</div>
)}
2025-09-15 19:46:52 +00:00
</Container>
)
}
2025-09-15 09:31:47 +00:00
2025-09-15 19:46:52 +00:00
export default OvertimeManagement