Organization Unit kaldırıldı
Employee List üzerinden yapılacak
This commit is contained in:
parent
13a3b56ab4
commit
bd670cc7d4
4 changed files with 10 additions and 2017 deletions
|
|
@ -14,7 +14,6 @@ using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Identity;
|
using Volo.Abp.Identity;
|
||||||
using Volo.Abp.TenantManagement;
|
using Volo.Abp.TenantManagement;
|
||||||
using AbpIdentity = Kurs.Platform.Data.Seeds.SeedConsts.AbpIdentity;
|
|
||||||
using AppCodes = Kurs.Platform.Data.Seeds.SeedConsts.AppCodes;
|
using AppCodes = Kurs.Platform.Data.Seeds.SeedConsts.AppCodes;
|
||||||
using static Kurs.Platform.PlatformConsts;
|
using static Kurs.Platform.PlatformConsts;
|
||||||
using static Kurs.Platform.PlatformSeeder.SeederDefaults;
|
using static Kurs.Platform.PlatformSeeder.SeederDefaults;
|
||||||
|
|
@ -1526,6 +1525,7 @@ public class ListFormSeeder_Hr : IDataSeedContributor, ITransientDependency
|
||||||
GroupPanelJson = JsonSerializer.Serialize(new { Visible = true }),
|
GroupPanelJson = JsonSerializer.Serialize(new { Visible = true }),
|
||||||
SelectionJson = DefaultSelectionSingleJson,
|
SelectionJson = DefaultSelectionSingleJson,
|
||||||
ColumnOptionJson = DefaultColumnOptionJson,
|
ColumnOptionJson = DefaultColumnOptionJson,
|
||||||
|
TreeOptionJson = DefaultTreeOptionJson("Id", "ManagerId", true),
|
||||||
PermissionJson = DefaultPermissionJson(AppCodes.Hr.Employee),
|
PermissionJson = DefaultPermissionJson(AppCodes.Hr.Employee),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Employee)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Employee)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson,
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson,
|
||||||
|
|
@ -2037,7 +2037,7 @@ public class ListFormSeeder_Hr : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.Guid,
|
SourceDbType = DbType.Guid,
|
||||||
FieldName = "JobPositionId",
|
FieldName = "JobPositionId",
|
||||||
Width = 100,
|
Width = 200,
|
||||||
ListOrderNo = 25,
|
ListOrderNo = 25,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
|
|
||||||
|
|
@ -897,13 +897,6 @@
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "admin.hr.organization",
|
|
||||||
"path": "/admin/hr/organization",
|
|
||||||
"componentPath": "@/views/hr/components/OrganizationChart",
|
|
||||||
"routeType": "protected",
|
|
||||||
"authority": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "admin.crm.customers",
|
"key": "admin.crm.customers",
|
||||||
"path": "/admin/crm/customers",
|
"path": "/admin/crm/customers",
|
||||||
|
|
@ -2805,21 +2798,11 @@
|
||||||
"RequiredPermissionName": "App.Hr.Employee",
|
"RequiredPermissionName": "App.Hr.Employee",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ParentCode": "App.Hr",
|
|
||||||
"Code": "App.Hr.Organization",
|
|
||||||
"DisplayName": "App.Hr.Organization",
|
|
||||||
"Order": 7,
|
|
||||||
"Url": "/admin/hr/organization",
|
|
||||||
"Icon": "FcTreeStructure",
|
|
||||||
"RequiredPermissionName": "App.Hr.Organization",
|
|
||||||
"IsDisabled": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Leave",
|
"Code": "App.Hr.Leave",
|
||||||
"DisplayName": "App.Hr.Leave",
|
"DisplayName": "App.Hr.Leave",
|
||||||
"Order": 8,
|
"Order": 7,
|
||||||
"Url": "/admin/list/list-leave",
|
"Url": "/admin/list/list-leave",
|
||||||
"Icon": "FcCalendar",
|
"Icon": "FcCalendar",
|
||||||
"RequiredPermissionName": "App.Hr.Leave",
|
"RequiredPermissionName": "App.Hr.Leave",
|
||||||
|
|
@ -2829,7 +2812,7 @@
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Overtime",
|
"Code": "App.Hr.Overtime",
|
||||||
"DisplayName": "App.Hr.Overtime",
|
"DisplayName": "App.Hr.Overtime",
|
||||||
"Order": 9,
|
"Order": 8,
|
||||||
"Url": "/admin/list/list-overtime",
|
"Url": "/admin/list/list-overtime",
|
||||||
"Icon": "FcClock",
|
"Icon": "FcClock",
|
||||||
"RequiredPermissionName": "App.Hr.Overtime",
|
"RequiredPermissionName": "App.Hr.Overtime",
|
||||||
|
|
@ -2839,7 +2822,7 @@
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Expense",
|
"Code": "App.Hr.Expense",
|
||||||
"DisplayName": "App.Hr.Expense",
|
"DisplayName": "App.Hr.Expense",
|
||||||
"Order": 10,
|
"Order": 9,
|
||||||
"Url": "/admin/list/list-expense",
|
"Url": "/admin/list/list-expense",
|
||||||
"Icon": "FaFileInvoiceDollar",
|
"Icon": "FaFileInvoiceDollar",
|
||||||
"RequiredPermissionName": "App.Hr.Expense",
|
"RequiredPermissionName": "App.Hr.Expense",
|
||||||
|
|
@ -2849,7 +2832,7 @@
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Payroll",
|
"Code": "App.Hr.Payroll",
|
||||||
"DisplayName": "App.Hr.Payroll",
|
"DisplayName": "App.Hr.Payroll",
|
||||||
"Order": 11,
|
"Order": 10,
|
||||||
"Url": "/admin/list/list-payroll",
|
"Url": "/admin/list/list-payroll",
|
||||||
"Icon": "FcMoneyTransfer",
|
"Icon": "FcMoneyTransfer",
|
||||||
"RequiredPermissionName": "App.Hr.Payroll",
|
"RequiredPermissionName": "App.Hr.Payroll",
|
||||||
|
|
@ -2859,7 +2842,7 @@
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Template360",
|
"Code": "App.Hr.Template360",
|
||||||
"DisplayName": "App.Hr.Template360",
|
"DisplayName": "App.Hr.Template360",
|
||||||
"Order": 12,
|
"Order": 11,
|
||||||
"Url": "/admin/list/list-template360",
|
"Url": "/admin/list/list-template360",
|
||||||
"Icon": "FcInspection",
|
"Icon": "FcInspection",
|
||||||
"RequiredPermissionName": "App.Hr.Template360",
|
"RequiredPermissionName": "App.Hr.Template360",
|
||||||
|
|
@ -2869,7 +2852,7 @@
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Performance360",
|
"Code": "App.Hr.Performance360",
|
||||||
"DisplayName": "App.Hr.Performance360",
|
"DisplayName": "App.Hr.Performance360",
|
||||||
"Order": 13,
|
"Order": 12,
|
||||||
"Url": "/admin/list/list-performance360",
|
"Url": "/admin/list/list-performance360",
|
||||||
"Icon": "FcFeedback",
|
"Icon": "FcFeedback",
|
||||||
"RequiredPermissionName": "App.Hr.Performance360",
|
"RequiredPermissionName": "App.Hr.Performance360",
|
||||||
|
|
@ -2879,7 +2862,7 @@
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Training",
|
"Code": "App.Hr.Training",
|
||||||
"DisplayName": "App.Hr.Training",
|
"DisplayName": "App.Hr.Training",
|
||||||
"Order": 14,
|
"Order": 13,
|
||||||
"Url": "/admin/list/list-training",
|
"Url": "/admin/list/list-training",
|
||||||
"Icon": "FcGraduationCap",
|
"Icon": "FcGraduationCap",
|
||||||
"RequiredPermissionName": "App.Hr.Training",
|
"RequiredPermissionName": "App.Hr.Training",
|
||||||
|
|
@ -2889,7 +2872,7 @@
|
||||||
"ParentCode": "App.Hr",
|
"ParentCode": "App.Hr",
|
||||||
"Code": "App.Hr.Survey",
|
"Code": "App.Hr.Survey",
|
||||||
"DisplayName": "App.Hr.Survey",
|
"DisplayName": "App.Hr.Survey",
|
||||||
"Order": 15,
|
"Order": 14,
|
||||||
"Url": "/admin/list/list-survey",
|
"Url": "/admin/list/list-survey",
|
||||||
"Icon": "FcSurvey",
|
"Icon": "FcSurvey",
|
||||||
"RequiredPermissionName": "App.Hr.Survey",
|
"RequiredPermissionName": "App.Hr.Survey",
|
||||||
|
|
|
||||||
|
|
@ -1,645 +0,0 @@
|
||||||
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 { HrOrganizationChart as OrgChart } from '../../../types/hr'
|
|
||||||
import { mockEmployees } from '../../../mocks/mockEmployees'
|
|
||||||
import { mockDepartments } from '../../../mocks/mockDepartments'
|
|
||||||
import Widget from '../../../components/common/Widget'
|
|
||||||
import { Container } from '@/components/shared'
|
|
||||||
import { EmployeeDto } from '@/proxy/intranet/models'
|
|
||||||
|
|
||||||
// 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.departmentId)
|
|
||||||
|
|
||||||
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: EmployeeDto
|
|
||||||
children: TreeNode[]
|
|
||||||
level: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const OrganizationChart: React.FC = () => {
|
|
||||||
const [employees] = useState<EmployeeDto[]>(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<EmployeeDto | null>(null)
|
|
||||||
const [showModal, setShowModal] = useState(false)
|
|
||||||
const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
|
|
||||||
|
|
||||||
const handleViewEmployee = (employee: EmployeeDto) => {
|
|
||||||
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.departmentId === 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.departmentId === 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, EmployeeDto[]>,
|
|
||||||
)
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Container>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-bold text-gray-900">Organizasyon Şeması</h2>
|
|
||||||
<p className="text-gray-600">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>
|
|
||||||
</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.phoneNumber && (
|
|
||||||
<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.phoneNumber}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{selectedEmployee.mobileNumber && (
|
|
||||||
<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.mobileNumber}
|
|
||||||
</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.phoneNumber}</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>
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OrganizationChart
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue