721 lines
26 KiB
TypeScript
721 lines
26 KiB
TypeScript
|
|
import React, { useState, useEffect, useMemo } from "react";
|
|||
|
|
import {
|
|||
|
|
FaUser,
|
|||
|
|
FaUsers,
|
|||
|
|
FaBuilding,
|
|||
|
|
FaChevronDown,
|
|||
|
|
FaChevronRight,
|
|||
|
|
FaEye,
|
|||
|
|
FaTh,
|
|||
|
|
FaSitemap,
|
|||
|
|
FaTimes,
|
|||
|
|
FaCalendar,
|
|||
|
|
FaEnvelope,
|
|||
|
|
FaPhone,
|
|||
|
|
FaMapMarkerAlt,
|
|||
|
|
FaBriefcase,
|
|||
|
|
} from "react-icons/fa";
|
|||
|
|
import { HrEmployee, HrOrganizationChart as OrgChart } from "../../../types/hr";
|
|||
|
|
import { mockEmployees } from "../../../mocks/mockEmployees";
|
|||
|
|
import { mockDepartments } from "../../../mocks/mockDepartments";
|
|||
|
|
import Widget from "../../../components/common/Widget";
|
|||
|
|
|
|||
|
|
// Dinamik organizasyon verisi oluşturma fonksiyonu
|
|||
|
|
const generateOrganizationData = (): OrgChart[] => {
|
|||
|
|
const orgData: OrgChart[] = [];
|
|||
|
|
|
|||
|
|
// Çalışanları işle
|
|||
|
|
mockEmployees.forEach((employee) => {
|
|||
|
|
const department = mockDepartments.find(
|
|||
|
|
(d) => d.id === employee.departmantId
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let level = 3; // Varsayılan seviye (normal çalışan)
|
|||
|
|
let parentId: string | undefined = undefined;
|
|||
|
|
|
|||
|
|
// Eğer bu çalışan bir departman yöneticisiyse
|
|||
|
|
if (department && department.managerId === employee.id) {
|
|||
|
|
if (!department.parentDepartmentId) {
|
|||
|
|
// Ana departman yöneticisi (CEO/Genel Müdür)
|
|||
|
|
level = 0;
|
|||
|
|
} else {
|
|||
|
|
// Alt departman yöneticisi
|
|||
|
|
level = 1;
|
|||
|
|
// Üst departmanın yöneticisini parent olarak bul
|
|||
|
|
const parentDept = mockDepartments.find(
|
|||
|
|
(d) => d.id === department.parentDepartmentId
|
|||
|
|
);
|
|||
|
|
if (parentDept && parentDept.managerId) {
|
|||
|
|
parentId = parentDept.managerId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Normal çalışan - departman yöneticisine bağlı
|
|||
|
|
if (department && department.managerId) {
|
|||
|
|
parentId = department.managerId;
|
|||
|
|
|
|||
|
|
// Departman yöneticisinin seviyesine göre belirleme
|
|||
|
|
const managerDept = mockDepartments.find(
|
|||
|
|
(d) => d.managerId === department.managerId
|
|||
|
|
);
|
|||
|
|
if (managerDept && !managerDept.parentDepartmentId) {
|
|||
|
|
level = 2; // CEO'ya direkt bağlı
|
|||
|
|
} else {
|
|||
|
|
level = 3; // Orta kademe yöneticiye bağlı
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
orgData.push({
|
|||
|
|
id: `org-${employee.id}`,
|
|||
|
|
employeeId: employee.id,
|
|||
|
|
employee: employee,
|
|||
|
|
parentId: parentId,
|
|||
|
|
level: level,
|
|||
|
|
position: employee.jobPosition?.name || "Belirsiz",
|
|||
|
|
isActive: employee.isActive,
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return orgData.sort((a, b) => a.level - b.level);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
interface TreeNode {
|
|||
|
|
employee: HrEmployee;
|
|||
|
|
children: TreeNode[];
|
|||
|
|
level: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const OrganizationChart: React.FC = () => {
|
|||
|
|
const [employees] = useState<HrEmployee[]>(mockEmployees);
|
|||
|
|
const [organizationData] = useState<OrgChart[]>(generateOrganizationData());
|
|||
|
|
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
|
|||
|
|
const [orgTree, setOrgTree] = useState<TreeNode[]>([]);
|
|||
|
|
const [viewMode, setViewMode] = useState<"tree" | "cards">("tree");
|
|||
|
|
const [selectedEmployee, setSelectedEmployee] = useState<HrEmployee | null>(
|
|||
|
|
null
|
|||
|
|
);
|
|||
|
|
const [showModal, setShowModal] = useState(false);
|
|||
|
|
const [selectedDepartment, setSelectedDepartment] = useState<string>("all");
|
|||
|
|
|
|||
|
|
const handleViewEmployee = (employee: HrEmployee) => {
|
|||
|
|
setSelectedEmployee(employee);
|
|||
|
|
setShowModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleCloseModal = () => {
|
|||
|
|
setShowModal(false);
|
|||
|
|
setSelectedEmployee(null);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
// Build organization tree structure
|
|||
|
|
const buildTree = (): TreeNode[] => {
|
|||
|
|
const nodeMap = new Map<string, TreeNode>();
|
|||
|
|
|
|||
|
|
// First, create all nodes
|
|||
|
|
employees.forEach((employee) => {
|
|||
|
|
nodeMap.set(employee.id, {
|
|||
|
|
employee,
|
|||
|
|
children: [],
|
|||
|
|
level: 0,
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Then, build the tree structure
|
|||
|
|
const rootNodes: TreeNode[] = [];
|
|||
|
|
|
|||
|
|
organizationData.forEach((orgData) => {
|
|||
|
|
const node = nodeMap.get(orgData.employeeId);
|
|||
|
|
if (node) {
|
|||
|
|
node.level = orgData.level;
|
|||
|
|
|
|||
|
|
if (orgData.parentId) {
|
|||
|
|
const parentNode = nodeMap.get(orgData.parentId);
|
|||
|
|
if (parentNode) {
|
|||
|
|
parentNode.children.push(node);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
rootNodes.push(node);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return rootNodes;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
setOrgTree(buildTree());
|
|||
|
|
// Auto-expand first level
|
|||
|
|
const firstLevelIds = organizationData
|
|||
|
|
.filter((org) => org.level <= 1)
|
|||
|
|
.map((org) => org.employeeId);
|
|||
|
|
setExpandedNodes(new Set(firstLevelIds));
|
|||
|
|
}, [employees, organizationData]);
|
|||
|
|
|
|||
|
|
// Filtered data for views
|
|||
|
|
const filteredOrgTree = useMemo(() => {
|
|||
|
|
if (selectedDepartment === "all") {
|
|||
|
|
return orgTree;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const filterTree = (nodes: TreeNode[]): TreeNode[] => {
|
|||
|
|
const result: TreeNode[] = [];
|
|||
|
|
for (const node of nodes) {
|
|||
|
|
const filteredChildren = filterTree(node.children);
|
|||
|
|
if (
|
|||
|
|
node.employee.departmantId === selectedDepartment ||
|
|||
|
|
filteredChildren.length > 0
|
|||
|
|
) {
|
|||
|
|
result.push({ ...node, children: filteredChildren });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return filterTree(orgTree);
|
|||
|
|
}, [orgTree, selectedDepartment]);
|
|||
|
|
|
|||
|
|
const filteredEmployees = useMemo(() => {
|
|||
|
|
if (selectedDepartment === "all") {
|
|||
|
|
return employees;
|
|||
|
|
}
|
|||
|
|
return employees.filter((emp) => emp.departmantId === selectedDepartment);
|
|||
|
|
}, [employees, selectedDepartment]);
|
|||
|
|
|
|||
|
|
const toggleNode = (nodeId: string) => {
|
|||
|
|
const newExpanded = new Set(expandedNodes);
|
|||
|
|
if (newExpanded.has(nodeId)) {
|
|||
|
|
newExpanded.delete(nodeId);
|
|||
|
|
} else {
|
|||
|
|
newExpanded.add(nodeId);
|
|||
|
|
}
|
|||
|
|
setExpandedNodes(newExpanded);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const renderTreeNode = (node: TreeNode): React.ReactNode => {
|
|||
|
|
const isExpanded = expandedNodes.has(node.employee.id);
|
|||
|
|
const hasChildren = node.children.length > 0;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div key={node.employee.id} className="relative">
|
|||
|
|
{/* Bağlantı çizgisi */}
|
|||
|
|
{node.level > 0 && (
|
|||
|
|
<span
|
|||
|
|
className="absolute border-l border-gray-300"
|
|||
|
|
style={{
|
|||
|
|
left: `${node.level * 1.25 - 0.7}rem`,
|
|||
|
|
top: 0,
|
|||
|
|
height: "100%",
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
<div
|
|||
|
|
className="flex items-center space-x-2 py-1.5 rounded-md hover:bg-gray-50"
|
|||
|
|
style={{ paddingLeft: `${node.level * 1.25}rem` }}
|
|||
|
|
>
|
|||
|
|
{/* Girinti ve bağlantı çizgisi */}
|
|||
|
|
{node.level > 0 && (
|
|||
|
|
<span
|
|||
|
|
className="absolute border-t border-gray-300 w-3"
|
|||
|
|
style={{ left: `${node.level * 1.25 - 0.7}rem`, top: "1.1rem" }}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Genişletme ikonu */}
|
|||
|
|
<div className="w-5 flex-shrink-0 text-center">
|
|||
|
|
{hasChildren && (
|
|||
|
|
<button
|
|||
|
|
onClick={() => toggleNode(node.employee.id)}
|
|||
|
|
className="p-1 text-gray-500 hover:text-gray-800 rounded-full hover:bg-gray-200"
|
|||
|
|
>
|
|||
|
|
{isExpanded ? (
|
|||
|
|
<FaChevronDown size={10} />
|
|||
|
|
) : (
|
|||
|
|
<FaChevronRight size={10} />
|
|||
|
|
)}
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Grup Bilgisi */}
|
|||
|
|
<div className="flex-grow flex items-center space-x-3">
|
|||
|
|
<div className="flex-1 min-w-0">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div>
|
|||
|
|
<h4 className="font-medium text-sm text-gray-900 truncate">
|
|||
|
|
{node.employee.fullName}
|
|||
|
|
</h4>
|
|||
|
|
<p className="text-xs text-gray-600 truncate">
|
|||
|
|
{node.employee.jobPosition?.name}
|
|||
|
|
</p>
|
|||
|
|
<p className="text-xs text-gray-500 truncate">
|
|||
|
|
{node.employee.department?.name}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center space-x-2 pr-2">
|
|||
|
|
<div className="flex items-center gap-2 ml-4">
|
|||
|
|
{hasChildren && (
|
|||
|
|
<span className="flex items-center text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
|
|||
|
|
<FaUsers className="w-3 h-3 mr-1" />
|
|||
|
|
{node.children.length}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<button // İşlemler
|
|||
|
|
onClick={() => handleViewEmployee(node.employee)}
|
|||
|
|
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
|
|||
|
|
title="Görüntüle"
|
|||
|
|
>
|
|||
|
|
<FaEye className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{hasChildren && isExpanded && (
|
|||
|
|
<div>{node.children.map((child) => renderTreeNode(child))}</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const renderCardView = () => {
|
|||
|
|
const groupedByLevel = filteredEmployees.reduce((acc, employee) => {
|
|||
|
|
const orgData = organizationData.find(
|
|||
|
|
(org) => org.employeeId === employee.id
|
|||
|
|
);
|
|||
|
|
const level = orgData?.level || 0;
|
|||
|
|
|
|||
|
|
if (!acc[level]) {
|
|||
|
|
acc[level] = [];
|
|||
|
|
}
|
|||
|
|
acc[level].push(employee);
|
|||
|
|
return acc;
|
|||
|
|
}, {} as Record<number, HrEmployee[]>);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="space-y-6">
|
|||
|
|
{Object.entries(groupedByLevel)
|
|||
|
|
.sort(([a], [b]) => parseInt(a) - parseInt(b))
|
|||
|
|
.map(([level, levelEmployees]) => (
|
|||
|
|
<div key={level}>
|
|||
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
|||
|
|
<FaBuilding className="w-5 h-5" />
|
|||
|
|
{level === "0"
|
|||
|
|
? "Üst Yönetim"
|
|||
|
|
: level === "1"
|
|||
|
|
? "Orta Kademe Yönetim"
|
|||
|
|
: level === "2"
|
|||
|
|
? "Alt Kademe Yönetim"
|
|||
|
|
: `${parseInt(level) + 1}. Seviye`}
|
|||
|
|
<span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
|
|||
|
|
{levelEmployees.length} kişi
|
|||
|
|
</span>
|
|||
|
|
</h3>
|
|||
|
|
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 gap-3">
|
|||
|
|
{levelEmployees.map((employee) => (
|
|||
|
|
<div
|
|||
|
|
key={employee.id}
|
|||
|
|
className="bg-white p-3 rounded-lg border shadow-sm hover:shadow-md transition-shadow"
|
|||
|
|
>
|
|||
|
|
<div className="flex items-center mb-3">
|
|||
|
|
<div
|
|||
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center mr-2 ${
|
|||
|
|
level === "0"
|
|||
|
|
? "bg-blue-100"
|
|||
|
|
: level === "1"
|
|||
|
|
? "bg-green-100"
|
|||
|
|
: "bg-gray-100"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<FaUser
|
|||
|
|
className={`w-4 h-4 ${
|
|||
|
|
level === "0"
|
|||
|
|
? "text-blue-600"
|
|||
|
|
: level === "1"
|
|||
|
|
? "text-green-600"
|
|||
|
|
: "text-gray-600"
|
|||
|
|
}`}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex-1 min-w-0">
|
|||
|
|
<h4
|
|||
|
|
onClick={() => handleViewEmployee(employee)}
|
|||
|
|
className="font-medium text-sm text-blue-600 hover:underline cursor-pointer truncate"
|
|||
|
|
>
|
|||
|
|
{employee.fullName}
|
|||
|
|
</h4>
|
|||
|
|
<p className="text-sm text-gray-600 truncate">
|
|||
|
|
{employee.code}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-1 mb-2">
|
|||
|
|
<p className="text-xs text-gray-800 font-medium truncate">
|
|||
|
|
{employee.jobPosition?.name}
|
|||
|
|
</p>
|
|||
|
|
<p className="text-xs text-gray-600 truncate">
|
|||
|
|
{employee.department?.name}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</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">
|
|||
|
|
Organizasyon Şeması
|
|||
|
|
</h2>
|
|||
|
|
<p className="text-sm text-gray-600 mt-1">
|
|||
|
|
Kurumsal hiyerarşi ve raporlama yapısı
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<select
|
|||
|
|
value={selectedDepartment}
|
|||
|
|
onChange={(e) => setSelectedDepartment(e.target.value)}
|
|||
|
|
className="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="all">Tüm Departmanlar</option>
|
|||
|
|
{mockDepartments.map((dept) => (
|
|||
|
|
<option key={dept.id} value={dept.id}>
|
|||
|
|
{dept.name}
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
<div className="flex bg-gray-100 rounded-lg p-0.5">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setViewMode("tree")}
|
|||
|
|
className={`p-1.5 rounded-md transition-colors ${
|
|||
|
|
viewMode === "tree"
|
|||
|
|
? "bg-white text-blue-600 shadow-sm"
|
|||
|
|
: "text-gray-600 hover:text-gray-900"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<FaSitemap 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"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<FaTh className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Stats */}
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|||
|
|
<Widget
|
|||
|
|
title="Toplam Personel"
|
|||
|
|
value={employees.length}
|
|||
|
|
color="blue"
|
|||
|
|
icon="FaBuilding"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<Widget
|
|||
|
|
title="Departmanlar"
|
|||
|
|
value={
|
|||
|
|
new Set(employees.map((e) => e.department?.id).filter(Boolean)).size
|
|||
|
|
}
|
|||
|
|
color="purple"
|
|||
|
|
icon="FaBuilding"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<Widget
|
|||
|
|
title="Seviye Sayısı"
|
|||
|
|
value={
|
|||
|
|
organizationData.length > 0
|
|||
|
|
? Math.max(...organizationData.map((org) => org.level)) + 1
|
|||
|
|
: 0
|
|||
|
|
}
|
|||
|
|
color="orange"
|
|||
|
|
icon="FaUser"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Content */}
|
|||
|
|
<div className="bg-white rounded-lg border p-4">
|
|||
|
|
{viewMode === "tree" ? (
|
|||
|
|
<div className="pt-2">
|
|||
|
|
{filteredOrgTree.map((node) => renderTreeNode(node))}
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
renderCardView()
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{(viewMode === "tree"
|
|||
|
|
? filteredOrgTree.length === 0
|
|||
|
|
: filteredEmployees.length === 0) && (
|
|||
|
|
<div className="text-center py-12">
|
|||
|
|
<FaBuilding className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
|||
|
|
<h3 className="text-base font-medium text-gray-900 mb-2">
|
|||
|
|
Organizasyon verisi bulunamadı
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-gray-500">
|
|||
|
|
Henüz organizasyon şeması oluşturulmamış.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Employee Detail Modal */}
|
|||
|
|
{showModal && selectedEmployee && (
|
|||
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
|||
|
|
<div className="bg-white rounded-lg p-4 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
|
|||
|
|
{/* Modal Header */}
|
|||
|
|
<div className="flex justify-between items-center mb-4">
|
|||
|
|
<h3 className="text-lg font-semibold text-gray-900">
|
|||
|
|
Personel Detayları
|
|||
|
|
</h3>
|
|||
|
|
<button
|
|||
|
|
onClick={handleCloseModal}
|
|||
|
|
className="text-gray-400 hover:text-gray-600 p-1"
|
|||
|
|
>
|
|||
|
|
<FaTimes className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Employee Info */}
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
{/* Basic Info */}
|
|||
|
|
<div className="flex items-center space-x-4">
|
|||
|
|
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
|
|||
|
|
<FaUser className="w-8 h-8 text-blue-600" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<h4 className="text-lg font-bold text-gray-900">
|
|||
|
|
{selectedEmployee.fullName}
|
|||
|
|
</h4>
|
|||
|
|
<p className="text-gray-600">{selectedEmployee.code}</p>
|
|||
|
|
<p className="text-sm text-gray-500">
|
|||
|
|
{selectedEmployee.jobPosition?.name}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Contact Information */}
|
|||
|
|
<div>
|
|||
|
|
<h5 className="text-base font-semibold text-gray-900 mb-2 flex items-center">
|
|||
|
|
<FaEnvelope className="w-5 h-5 mr-2 text-blue-600" />
|
|||
|
|
İletişim Bilgileri
|
|||
|
|
</h5>
|
|||
|
|
<div className="bg-gray-50 p-3 rounded-lg space-y-2">
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<FaEnvelope className="w-4 h-4 text-gray-500 mr-3" />
|
|||
|
|
<span className="text-sm">E-posta:</span>
|
|||
|
|
<span className="ml-2 text-sm font-medium">
|
|||
|
|
{selectedEmployee.email}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
{selectedEmployee.phone && (
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<FaPhone className="w-4 h-4 text-gray-500 mr-3" />
|
|||
|
|
<span className="text-sm">İş Telefonu:</span>
|
|||
|
|
<span className="ml-2 text-sm font-medium">
|
|||
|
|
{selectedEmployee.phone}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
{selectedEmployee.personalPhone && (
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<FaPhone className="w-4 h-4 text-gray-500 mr-3" />
|
|||
|
|
<span className="text-sm">Kişisel Telefon:</span>
|
|||
|
|
<span className="ml-2 text-sm font-medium">
|
|||
|
|
{selectedEmployee.personalPhone}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Employment Information */}
|
|||
|
|
<div>
|
|||
|
|
<h5 className="text-base font-semibold text-gray-900 mb-2 flex items-center">
|
|||
|
|
<FaBriefcase className="w-5 h-5 mr-2 text-green-600" />
|
|||
|
|
İş Bilgileri
|
|||
|
|
</h5>
|
|||
|
|
<div className="bg-gray-50 p-3 rounded-lg space-y-2">
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">Departman:</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.department?.name}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">Pozisyon:</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.jobPosition?.name}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">
|
|||
|
|
İşe Başlama Tarihi:
|
|||
|
|
</span>
|
|||
|
|
<p className="font-medium flex items-center">
|
|||
|
|
<FaCalendar className="w-4 h-4 mr-1 text-gray-500" />
|
|||
|
|
{new Date(selectedEmployee.hireDate).toLocaleDateString(
|
|||
|
|
"tr-TR"
|
|||
|
|
)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">
|
|||
|
|
İstihdam Türü:
|
|||
|
|
</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.employmentType}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">
|
|||
|
|
Çalışma Lokasyonu:
|
|||
|
|
</span>
|
|||
|
|
<p className="font-medium flex items-center">
|
|||
|
|
<FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500" />
|
|||
|
|
{selectedEmployee.workLocation}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">Durum:</span>
|
|||
|
|
<span
|
|||
|
|
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
|
|||
|
|
selectedEmployee.isActive
|
|||
|
|
? "bg-green-100 text-green-800"
|
|||
|
|
: "bg-red-100 text-red-800"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{selectedEmployee.isActive ? "Aktif" : "Pasif"}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Personal Information */}
|
|||
|
|
<div>
|
|||
|
|
<h5 className="text-base font-semibold text-gray-900 mb-2 flex items-center">
|
|||
|
|
<FaUser className="w-5 h-5 mr-2 text-purple-600" />
|
|||
|
|
Kişisel Bilgiler
|
|||
|
|
</h5>
|
|||
|
|
<div className="bg-gray-50 p-3 rounded-lg space-y-2">
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">
|
|||
|
|
Doğum Tarihi:
|
|||
|
|
</span>
|
|||
|
|
<p className="font-medium flex items-center">
|
|||
|
|
<FaCalendar className="w-4 h-4 mr-1 text-gray-500" />
|
|||
|
|
{new Date(
|
|||
|
|
selectedEmployee.birthDate
|
|||
|
|
).toLocaleDateString("tr-TR")}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">Cinsiyet:</span>
|
|||
|
|
<p className="font-medium">{selectedEmployee.gender}</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">
|
|||
|
|
Medeni Durum:
|
|||
|
|
</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.maritalStatus}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">
|
|||
|
|
TC Kimlik No:
|
|||
|
|
</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.nationalId}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{selectedEmployee.address && (
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">Adres:</span>
|
|||
|
|
<p className="font-medium flex items-start">
|
|||
|
|
<FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500 mt-0.5" />
|
|||
|
|
{selectedEmployee.address.street},{" "}
|
|||
|
|
{selectedEmployee.address.city},{" "}
|
|||
|
|
{selectedEmployee.address.country}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Emergency Contact */}
|
|||
|
|
{selectedEmployee.emergencyContact && (
|
|||
|
|
<div>
|
|||
|
|
<h5 className="text-base font-semibold text-gray-900 mb-2 flex items-center">
|
|||
|
|
<FaPhone className="w-5 h-5 mr-2 text-red-600" />
|
|||
|
|
Acil Durum İletişim
|
|||
|
|
</h5>
|
|||
|
|
<div className="bg-gray-50 p-3 rounded-lg">
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">İsim:</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.emergencyContact.name}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">Yakınlık:</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.emergencyContact.relationship}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-sm text-gray-600">Telefon:</span>
|
|||
|
|
<p className="font-medium">
|
|||
|
|
{selectedEmployee.emergencyContact.phone}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Modal Footer */}
|
|||
|
|
<div className="flex justify-end gap-3 mt-4 pt-4 border-t">
|
|||
|
|
<button
|
|||
|
|
onClick={handleCloseModal}
|
|||
|
|
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|||
|
|
>
|
|||
|
|
Kapat
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default OrganizationChart;
|