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

1241 lines
43 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +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";
const OvertimeManagement: React.FC = () => {
// Overtime states
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");
// Overtime modal states
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("");
// Overtime form state
const [overtimeFormData, setOvertimeFormData] = useState({
employeeId: "",
date: "",
startTime: "",
endTime: "",
reason: "",
rate: 1.5,
});
// Bulk overtime form state
const [bulkOvertimeFormData, setBulkOvertimeFormData] = useState({
departmentId: "",
selectedEmployees: [] as string[],
date: "",
startTime: "",
endTime: "",
reason: "",
rate: 1.5,
});
// Get employees by department
const getEmployeesByDepartment = (departmentId: string) => {
return mockEmployees.filter((emp) => emp.departmantId === departmentId);
};
// Get selected employee object
const getSelectedEmployee = (employeeId: string) => {
return mockEmployees.find((emp) => emp.id === employeeId);
};
// Overtime handlers
const handleOvertimeAdd = () => {
setOvertimeFormData({
employeeId: "",
date: "",
startTime: "",
endTime: "",
reason: "",
rate: 1.5,
});
setShowOvertimeAddModal(true);
};
const handleOvertimeEdit = (overtime: HrOvertime) => {
setSelectedOvertime(overtime);
setOvertimeFormData({
employeeId: overtime.employeeId,
date: new Date(overtime.date).toISOString().split("T")[0],
startTime: overtime.startTime,
endTime: overtime.endTime,
reason: overtime.reason,
rate: overtime.rate || 1.5,
});
setShowOvertimeEditModal(true);
};
const handleOvertimeApprove = (id: string) => {
setOvertimes((prevOvertimes) =>
prevOvertimes.map((overtime) =>
overtime.id === id
? {
...overtime,
status: LeaveStatusEnum.Approved,
lastModificationTime: new Date(),
}
: overtime
)
);
alert("Mesai talebi onaylandı!");
};
const handleOvertimeReject = (id: string, reason?: string) => {
if (reason) {
setOvertimes((prevOvertimes) =>
prevOvertimes.map((overtime) =>
overtime.id === id
? {
...overtime,
status: LeaveStatusEnum.Rejected,
lastModificationTime: new Date(),
}
: overtime
)
);
setShowOvertimeRejectModal(false);
setOvertimeRejectReason("");
alert("Mesai talebi reddedildi!");
} else {
const overtime = overtimes.find((o) => o.id === id);
if (overtime) {
setSelectedOvertime(overtime);
setShowOvertimeRejectModal(true);
}
}
};
const handleOvertimeView = (overtime: HrOvertime) => {
setSelectedOvertime(overtime);
setShowOvertimeViewModal(true);
};
const handleSubmitOvertimeAdd = () => {
if (
!overtimeFormData.employeeId ||
!overtimeFormData.date ||
!overtimeFormData.startTime ||
!overtimeFormData.endTime
) {
alert("Lütfen tüm gerekli alanları doldurun!");
return;
}
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);
if (totalHours <= 0) {
alert("Bitiş saati başlangıç saatinden sonra olmalıdır!");
return;
}
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(),
};
setOvertimes((prevOvertimes) => [...prevOvertimes, newOvertime]);
setShowOvertimeAddModal(false);
alert("Mesai talebi başarıyla oluşturuldu!");
};
const handleSubmitOvertimeEdit = () => {
if (!selectedOvertime) return;
if (
!overtimeFormData.employeeId ||
!overtimeFormData.date ||
!overtimeFormData.startTime ||
!overtimeFormData.endTime
) {
alert("Lütfen tüm gerekli alanları doldurun!");
return;
}
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);
if (totalHours <= 0) {
alert("Bitiş saati başlangıç saatinden sonra olmalıdır!");
return;
}
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(),
}
: overtime
)
);
setShowOvertimeEditModal(false);
setSelectedOvertime(null);
alert("Mesai talebi başarıyla güncellendi!");
};
const handleSubmitOvertimeReject = () => {
if (!selectedOvertime || !overtimeRejectReason.trim()) {
alert("Lütfen red nedeni giriniz!");
return;
}
handleOvertimeReject(selectedOvertime.id, overtimeRejectReason);
};
const handleBulkOvertimeAdd = () => {
setBulkOvertimeFormData({
departmentId: "",
selectedEmployees: [],
date: "",
startTime: "",
endTime: "",
reason: "",
rate: 1.5,
});
setShowBulkOvertimeModal(true);
};
const handleSubmitBulkOvertime = () => {
if (
!bulkOvertimeFormData.selectedEmployees.length ||
!bulkOvertimeFormData.date ||
!bulkOvertimeFormData.startTime ||
!bulkOvertimeFormData.endTime
) {
alert(
"Lütfen en az bir personel seçin ve tüm gerekli alanları doldurun!"
);
return;
}
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);
if (totalHours <= 0) {
alert("Bitiş saati başlangıç saatinden sonra olmalıdır!");
return;
}
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);
setBulkOvertimeFormData({
departmentId: "",
selectedEmployees: [],
date: "",
startTime: "",
endTime: "",
reason: "",
rate: 1.5,
});
alert(
`${newOvertimes.length} personel için toplu mesai talebi başarıyla oluşturuldu!`
);
};
// Filter overtime data
const filteredOvertimes = overtimes.filter((overtime) => {
if (
overtimeSelectedStatus !== "all" &&
overtime.status !== overtimeSelectedStatus
) {
return false;
}
if (
selectedDepartment !== "all" &&
overtime.employee?.department?.name !== selectedDepartment
) {
return false;
}
if (overtimeSelectedPeriod !== "all") {
const now = new Date();
const overtimeDate = new Date(overtime.date);
switch (overtimeSelectedPeriod) {
case "this-month": {
const currentMonth = now.getMonth();
const currentYear = now.getFullYear();
return (
overtimeDate.getMonth() === currentMonth &&
overtimeDate.getFullYear() === currentYear
);
}
case "last-month": {
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1);
return (
overtimeDate.getMonth() === lastMonth.getMonth() &&
overtimeDate.getFullYear() === lastMonth.getFullYear()
);
}
case "last-3-months": {
const threeMonthsAgo = new Date(
now.getFullYear(),
now.getMonth() - 3
);
return overtimeDate >= threeMonthsAgo;
}
default:
return true;
}
}
// Search filter
if (searchOvertimesTerm.trim() !== "") {
if (overtime.employeeId !== searchOvertimesTerm) {
return false;
}
}
return true;
});
// Overtime table columns
const overtimeColumns: Column<HrOvertime>[] = [
{
key: "employee",
header: "Personel",
render: (overtime: HrOvertime) => {
const employee = mockEmployees.find(
(emp) => emp.id === overtime.employeeId
);
return (
<div className="flex items-center gap-2">
<FaUser className="w-4 h-4 text-gray-500" />
<div>
<div className="font-medium text-gray-900">
{employee?.fullName}
</div>
<div className="text-sm text-gray-500">{employee?.code}</div>
</div>
</div>
);
},
},
{
key: "date",
header: "Tarih",
render: (overtime: HrOvertime) => (
<div className="text-sm">
{new Date(overtime.date).toLocaleDateString("tr-TR")}
</div>
),
},
{
key: "time",
header: "Mesai Saatleri",
render: (overtime: HrOvertime) => (
<div className="text-sm">
<div>
{overtime.startTime} - {overtime.endTime}
</div>
<div className="text-gray-500">{overtime.totalHours} saat</div>
</div>
),
},
{
key: "reason",
header: "Neden",
render: (overtime: HrOvertime) => (
<div className="text-sm max-w-xs truncate" title={overtime.reason}>
{overtime.reason}
</div>
),
},
{
key: "rate",
header: "Çarpan",
render: (overtime: HrOvertime) => (
<div className="text-sm">x{overtime.rate}</div>
),
},
{
key: "amount",
header: "Tutar",
render: (overtime: HrOvertime) => (
<div className="text-sm font-medium">
{overtime.amount?.toLocaleString("tr-TR")}
</div>
),
},
{
key: "status",
header: "Durum",
render: (overtime: HrOvertime) => (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getLeaveStatusColor(
overtime.status
)}`}
>
{getLeaveStatusText(overtime.status)}
</span>
),
},
{
key: "actions",
header: "İşlemler",
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>
),
},
];
return (
<div className="space-y-3 pt-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-gray-900">Mesai Yönetimi</h2>
<p className="text-sm text-gray-600 mt-1">
Personel mesai talepleri ve onay süreçleri
</p>
</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>
</div>
{/* 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"
>
<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"
>
<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>
<option value={LeaveStatusEnum.Pending}>Beklemede</option>
<option value={LeaveStatusEnum.Approved}>Onaylı</option>
<option value={LeaveStatusEnum.Rejected}>Reddedildi</option>
</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>
</div>
{/* Data Table */}
<div className="bg-white rounded-lg shadow-sm border">
<DataTable data={filteredOvertimes} columns={overtimeColumns} />
</div>
{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>
)}
{/* Overtime Add/Edit Modal */}
{(showOvertimeAddModal || showOvertimeEditModal) && 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">
<h3 className="text-base font-semibold mb-4">
{showOvertimeAddModal
? "Yeni Mesai Talebi"
: "Mesai Talebini Düzenle"}
</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Personel
</label>
<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>
<label className="block text-sm font-medium text-gray-700 mb-1">
Tarih
</label>
<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>
<label className="block text-sm font-medium text-gray-700 mb-1">
Neden
</label>
<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={() => {
setShowOvertimeAddModal(false);
setShowOvertimeEditModal(false);
setSelectedOvertime(null);
}}
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={
showOvertimeAddModal
? handleSubmitOvertimeAdd
: handleSubmitOvertimeEdit
}
className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
{showOvertimeAddModal ? "Oluştur" : "Güncelle"}
</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>
<label className="block text-sm font-medium text-gray-700">
Personel
</label>
<p className="text-gray-900">
{mockEmployees.find(
(emp) => emp.id === selectedOvertime.employeeId
)?.fullName || selectedOvertime.employeeId}
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Tarih
</label>
<p className="text-gray-900">
{new Date(selectedOvertime.date).toLocaleDateString(
"tr-TR"
)}
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700">
Başlangıç Saati
</label>
<p className="text-gray-900">{selectedOvertime.startTime}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Bitiş Saati
</label>
<p className="text-gray-900">{selectedOvertime.endTime}</p>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700">
Toplam Saat
</label>
<p className="text-gray-900">
{selectedOvertime.totalHours} saat
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Çarpan
</label>
<p className="text-gray-900">x{selectedOvertime.rate}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Tutar
</label>
<p className="text-gray-900 font-medium">
{selectedOvertime.amount?.toLocaleString("tr-TR")}
</p>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Durum
</label>
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getLeaveStatusColor(
selectedOvertime.status
)}`}
>
{getLeaveStatusText(selectedOvertime.status)}
</span>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Neden
</label>
<p className="text-gray-900">{selectedOvertime.reason}</p>
</div>
{selectedOvertime.approvedBy && (
<div>
<label className="block text-sm font-medium text-gray-700">
Onaylayan
</label>
<p className="text-gray-900">
{
mockEmployees.find(
(emp) => emp.id === selectedOvertime.approvedBy
)?.fullName
}
</p>
</div>
)}
</div>
<div className="flex justify-end mt-6">
<button
onClick={() => {
setShowOvertimeViewModal(false);
setSelectedOvertime(null);
}}
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">
<h3 className="text-base font-semibold mb-4">
Mesai Talebini Reddet
</h3>
<div className="mb-4">
<p className="text-gray-700 mb-2">
<strong>
{mockEmployees.find(
(emp) => emp.id === selectedOvertime.employeeId
)?.fullName || selectedOvertime.employeeId}
</strong>{" "}
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={() => {
setShowOvertimeRejectModal(false);
setSelectedOvertime(null);
setOvertimeRejectReason("");
}}
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>
<label className="block text-sm font-medium text-gray-700 mb-1">
Departman
</label>
<select
value={bulkOvertimeFormData.departmentId}
onChange={(e) => {
setBulkOvertimeFormData({
...bulkOvertimeFormData,
departmentId: e.target.value,
selectedEmployees: [],
});
}}
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={
getEmployeesByDepartment(
bulkOvertimeFormData.departmentId
).length > 0 &&
bulkOvertimeFormData.selectedEmployees.length ===
getEmployeesByDepartment(
bulkOvertimeFormData.departmentId
).length
}
onChange={(e) => {
const departmentEmployees =
getEmployeesByDepartment(
bulkOvertimeFormData.departmentId
);
setBulkOvertimeFormData({
...bulkOvertimeFormData,
selectedEmployees: e.target.checked
? departmentEmployees.map((emp) => emp.id)
: [],
});
}}
className="mr-2"
/>
<span className="font-medium">Tümünü Seç</span>
</label>
{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>
))}
</div>
</div>
<p className="text-sm text-gray-500 mt-1">
{bulkOvertimeFormData.selectedEmployees.length} personel
seçildi
</p>
</div>
)}
{/* Date */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Mesai Tarihi
</label>
<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">
<h4 className="text-sm font-medium text-blue-900 mb-2">
Özet
</h4>
<div className="text-sm text-blue-800 space-y-1">
<p>
<span className="font-medium">Personel Sayısı:</span>{" "}
{bulkOvertimeFormData.selectedEmployees.length}
</p>
{(() => {
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);
const totalAmount =
totalHours *
100 *
bulkOvertimeFormData.rate *
bulkOvertimeFormData.selectedEmployees.length;
return totalHours > 0 ? (
<>
<p>
<span className="font-medium">Mesai Süresi:</span>{" "}
{totalHours} saat
</p>
<p>
<span className="font-medium">Çarpan:</span> x
{bulkOvertimeFormData.rate}
</p>
<p>
<span className="font-medium">Toplam Tutar:</span>{" "}
{totalAmount.toLocaleString("tr-TR")}
</p>
</>
) : null;
})()}
</div>
</div>
)}
</div>
<div className="flex justify-end gap-2 mt-6">
<button
onClick={() => {
setShowBulkOvertimeModal(false);
setBulkOvertimeFormData({
departmentId: "",
selectedEmployees: [],
date: "",
startTime: "",
endTime: "",
reason: "",
rate: 1.5,
});
}}
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>
)}
</div>
);
};
export default OvertimeManagement;