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

493 lines
16 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
import React, { useState } from "react";
import {
FaPlus,
FaEdit,
FaTrash,
FaBuilding,
FaUsers,
FaDollarSign,
FaEye,
FaList,
FaTh,
} from "react-icons/fa";
import { HrDepartment } from "../../../types/hr";
import DataTable, { Column } from "../../../components/common/DataTable";
import { mockDepartments } from "../../../mocks/mockDepartments";
import { mockEmployees } from "../../../mocks/mockEmployees";
import { mockCostCenters } from "../../../mocks/mockCostCenters";
import DepartmentFormModal from "./DepartmentFormModal";
import DepartmentViewModal from "./DepartmentViewModal";
import Widget from "../../../components/common/Widget";
const DepartmentManagement: React.FC = () => {
const [departments, setDepartments] =
useState<HrDepartment[]>(mockDepartments);
const [searchTerm, setSearchTerm] = useState("");
const [selectedParent, setSelectedParent] = useState<string>("all");
const [viewMode, setViewMode] = useState<"list" | "cards">("list");
// Modal states
const [isFormModalOpen, setIsFormModalOpen] = useState(false);
const [isViewModalOpen, setIsViewModalOpen] = useState(false);
const [selectedDepartment, setSelectedDepartment] = useState<
HrDepartment | undefined
>();
const [modalTitle, setModalTitle] = useState("");
const handleAdd = () => {
setSelectedDepartment(undefined);
setModalTitle("Yeni Departman");
setIsFormModalOpen(true);
};
const handleEdit = (department: HrDepartment) => {
setSelectedDepartment(department);
setModalTitle("Departman Düzenle");
setIsFormModalOpen(true);
};
const handleView = (department: HrDepartment) => {
setSelectedDepartment(department);
setIsViewModalOpen(true);
};
const handleDelete = (id: string) => {
if (confirm("Bu departmanı silmek istediğinizden emin misiniz?")) {
setDepartments((prev) => prev.filter((dept) => dept.id !== id));
}
};
const handleSave = (departmentData: Partial<HrDepartment>) => {
if (selectedDepartment) {
// Edit existing department
setDepartments((prev) =>
prev.map((dept) =>
dept.id === selectedDepartment.id
? { ...dept, ...departmentData, lastModificationTime: new Date() }
: dept
)
);
} else {
// Add new department
const newDepartment: HrDepartment = {
id: `dept_${Date.now()}`,
...departmentData,
subDepartments: [],
creationTime: new Date(),
lastModificationTime: new Date(),
} as HrDepartment;
setDepartments((prev) => [...prev, newDepartment]);
}
setIsFormModalOpen(false);
};
const handleCloseFormModal = () => {
setIsFormModalOpen(false);
setSelectedDepartment(undefined);
};
const handleCloseViewModal = () => {
setIsViewModalOpen(false);
setSelectedDepartment(undefined);
};
const handleEditFromView = (department: HrDepartment) => {
setIsViewModalOpen(false);
handleEdit(department);
};
mockDepartments.forEach((dept) => {
if (dept.managerId) {
dept.manager = mockEmployees.find((emp) => emp.id === dept.managerId);
}
if (dept.costCenterId) {
dept.costCenter = mockCostCenters.find(
(cc) => cc.id === dept.costCenterId
);
}
});
const filteredDepartments = departments.filter((department) => {
if (
searchTerm &&
!department.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
!department.code.toLowerCase().includes(searchTerm.toLowerCase())
) {
return false;
}
if (
selectedParent !== "all" &&
department.parentDepartmentId !== selectedParent
) {
return false;
}
return true;
});
const columns: Column<HrDepartment>[] = [
{
key: "code",
header: "Departman Kodu",
sortable: true,
},
{
key: "name",
header: "Departman Adı",
sortable: true,
},
{
key: "parentDepartment",
header: "Üst Departman",
render: (department: HrDepartment) =>
department.parentDepartment?.name || "-",
},
{
key: "manager",
header: "Yönetici",
render: (department: HrDepartment) => department.manager?.fullName || "-",
},
{
key: "employeeCount",
header: "Personel Sayısı",
render: (department: HrDepartment) => (
<div className="flex items-center gap-1">
<FaUsers className="w-4 h-4 text-gray-500" />
<span>
{mockEmployees.filter((a) => a.departmantId == department.id)
.length || 0}
</span>
</div>
),
},
{
key: "costCenter",
header: "Maliyet Merkezi",
render: (department: HrDepartment) => (
<div className="flex flex-col">
<span className="font-medium">
{department.costCenter?.code || "-"}
</span>
<span className="text-xs text-gray-500">
{department.costCenter?.name || "-"}
</span>
</div>
),
},
{
key: "budget",
header: "Bütçe",
render: (department: HrDepartment) => (
<div className="flex items-center gap-1">
<FaDollarSign className="w-4 h-4 text-gray-500" />
<span>
{department.budget ? `${department.budget.toLocaleString()}` : "-"}
</span>
</div>
),
},
{
key: "status",
header: "Durum",
render: (department: HrDepartment) => (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${
department.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{department.isActive ? "Aktif" : "Pasif"}
</span>
),
},
{
key: "actions",
header: "İşlemler",
render: (department: HrDepartment) => (
<div className="flex gap-2">
<button
onClick={() => handleView(department)}
className="p-1 text-green-600 hover:bg-green-50 rounded"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(department)}
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(department.id)}
className="p-1 text-red-600 hover:bg-red-50 rounded"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
),
},
];
return (
<div className="space-y-3 pt-2">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 className="text-lg font-bold text-gray-900">
Departman Yönetimi
</h2>
<p className="text-sm text-gray-600 mt-1">
Organizasyon yapısını ve departmanları yönetin
</p>
</div>
<div className="flex items-center gap-3">
{/* View Mode Toggle */}
<div className="flex bg-gray-100 rounded-lg p-0.5">
<button
onClick={() => setViewMode("list")}
className={`p-1.5 rounded-md transition-colors ${
viewMode === "list"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
}`}
title="Liste Görünümü"
>
<FaList className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode("cards")}
className={`p-1.5 rounded-md transition-colors ${
viewMode === "cards"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
}`}
title="Kart Görünümü"
>
<FaTh className="w-4 h-4" />
</button>
</div>
<button
onClick={handleAdd}
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<FaPlus className="w-4 h-4" />
Yeni Departman
</button>
</div>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<Widget
title="Toplam Departman"
value={departments.length}
color="blue"
icon="FaBuilding"
/>
<Widget
title="Toplam Personel"
value={mockEmployees.length}
color="green"
icon="FaUsers"
/>
<Widget
title="Ana Departmanlar"
value={departments.filter((d) => !d.parentDepartmentId).length}
color="purple"
icon="FaBuilding"
/>
<Widget
title="Toplam Bütçe"
value={`${departments
.reduce((total, dept) => total + (dept.budget || 0), 0)
.toLocaleString()}`}
color="orange"
icon="FaDollarSign"
/>
</div>
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
<div className="flex-1">
<input
type="text"
placeholder="Departman adı veya kodu ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(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>
<select
value={selectedParent}
onChange={(e) => setSelectedParent(e.target.value)}
className="px-3 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[200px]"
>
<option value="all">Tüm Departmanlar</option>
{departments
.filter((d) => !d.parentDepartmentId)
.map((dept) => (
<option key={dept.id} value={dept.id}>
{dept.name} Alt Departmanları
</option>
))}
</select>
</div>
{/* Data Display */}
{viewMode === "list" ? (
<div className="bg-white rounded-lg shadow-sm border overflow-x-auto">
<DataTable data={filteredDepartments} columns={columns} />
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
{filteredDepartments.map((department) => (
<div
key={department.id}
className="bg-white rounded-lg shadow-sm border hover:shadow-md transition-shadow p-3"
>
{/* Card Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center space-x-3">
<div
className={`w-12 h-12 rounded-lg flex items-center justify-center ${
department.isActive
? "bg-blue-100 text-blue-600"
: "bg-gray-100 text-gray-500"
}`}
>
<FaBuilding className="w-6 h-6" />
</div>
<div>
<h3 className="font-semibold text-sm text-gray-900 truncate">
{department.name}
</h3>
<p className="text-sm text-gray-600">{department.code}</p>
</div>
</div>
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${
department.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{department.isActive ? "Aktif" : "Pasif"}
</span>
</div>
{/* Card Content */}
<div className="space-y-2">
{department.parentDepartment && (
<div className="flex items-center text-xs text-gray-600">
<FaBuilding className="w-4 h-4 mr-2 text-gray-400" />
<span>Üst: {department.parentDepartment.name}</span>
</div>
)}
{department.manager && (
<div className="flex items-center text-xs text-gray-600">
<FaUsers className="w-4 h-4 mr-2 text-gray-400" />
<span>Yönetici: {department.manager.fullName}</span>
</div>
)}
<div className="flex items-center text-xs text-gray-600">
<FaUsers className="w-4 h-4 mr-2 text-gray-400" />
<span>
{
mockEmployees.filter(
(emp) => emp.departmantId === department.id
).length
}{" "}
Personel
</span>
</div>
{department.budget && (
<div className="flex items-center text-xs text-gray-600">
<FaDollarSign className="w-4 h-4 mr-2 text-gray-400" />
<span>{department.budget.toLocaleString()}</span>
</div>
)}
{department.costCenter && (
<div className="text-xs text-gray-600">
<div className="font-medium">
{department.costCenter.code}
</div>
<div className="text-xs text-gray-500">
{department.costCenter.name}
</div>
</div>
)}
</div>
{/* Card Actions */}
<div className="flex justify-end space-x-1 mt-3 pt-3 border-t border-gray-100">
<button
onClick={() => handleView(department)}
className="p-1.5 text-green-600 hover:bg-green-50 rounded-lg transition-colors"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(department)}
className="p-1.5 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(department.id)}
className="p-1.5 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
))}
</div>
)}
{filteredDepartments.length === 0 && (
<div className="text-center py-12">
<FaBuilding className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
Departman bulunamadı
</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirmeyi deneyin.
</p>
</div>
)}
{/* Modals */}
<DepartmentFormModal
isOpen={isFormModalOpen}
onClose={handleCloseFormModal}
onSave={handleSave}
department={selectedDepartment}
title={modalTitle}
/>
<DepartmentViewModal
isOpen={isViewModalOpen}
onClose={handleCloseViewModal}
department={selectedDepartment || null}
onEdit={handleEditFromView}
/>
</div>
);
};
export default DepartmentManagement;