erp-platform/ui/src/views/hr/components/OvertimeManagement.tsx
2025-09-15 12:31:47 +03:00

1240 lines
43 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;