From bd670cc7d41e78f36336f5df0c9920ca3ace2320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sun, 9 Nov 2025 14:13:14 +0300 Subject: [PATCH] =?UTF-8?q?Organization=20Unit=20kald=C4=B1r=C4=B1ld=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Employee List üzerinden yapılacak --- .../Seeds/ListFormSeeder_Hr.cs | 4 +- .../Seeds/MenusData.json | 33 +- .../views/hr/components/OrganizationChart.tsx | 645 -------- .../views/hr/components/PayrollManagement.tsx | 1345 ----------------- 4 files changed, 10 insertions(+), 2017 deletions(-) delete mode 100644 ui/src/views/hr/components/OrganizationChart.tsx delete mode 100644 ui/src/views/hr/components/PayrollManagement.tsx diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs b/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs index 7138966b..fb06519d 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs @@ -14,7 +14,6 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; using Volo.Abp.TenantManagement; -using AbpIdentity = Kurs.Platform.Data.Seeds.SeedConsts.AbpIdentity; using AppCodes = Kurs.Platform.Data.Seeds.SeedConsts.AppCodes; using static Kurs.Platform.PlatformConsts; using static Kurs.Platform.PlatformSeeder.SeederDefaults; @@ -1526,6 +1525,7 @@ public class ListFormSeeder_Hr : IDataSeedContributor, ITransientDependency GroupPanelJson = JsonSerializer.Serialize(new { Visible = true }), SelectionJson = DefaultSelectionSingleJson, ColumnOptionJson = DefaultColumnOptionJson, + TreeOptionJson = DefaultTreeOptionJson("Id", "ManagerId", true), PermissionJson = DefaultPermissionJson(AppCodes.Hr.Employee), DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Employee)), DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson, @@ -2037,7 +2037,7 @@ public class ListFormSeeder_Hr : IDataSeedContributor, ITransientDependency CultureName = LanguageCodes.En, SourceDbType = DbType.Guid, FieldName = "JobPositionId", - Width = 100, + Width = 200, ListOrderNo = 25, Visible = true, IsActive = true, diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json index 45853c9a..91f4f48f 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json @@ -897,13 +897,6 @@ "routeType": "protected", "authority": null }, - { - "key": "admin.hr.organization", - "path": "/admin/hr/organization", - "componentPath": "@/views/hr/components/OrganizationChart", - "routeType": "protected", - "authority": null - }, { "key": "admin.crm.customers", "path": "/admin/crm/customers", @@ -2805,21 +2798,11 @@ "RequiredPermissionName": "App.Hr.Employee", "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", "Code": "App.Hr.Leave", "DisplayName": "App.Hr.Leave", - "Order": 8, + "Order": 7, "Url": "/admin/list/list-leave", "Icon": "FcCalendar", "RequiredPermissionName": "App.Hr.Leave", @@ -2829,7 +2812,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Overtime", "DisplayName": "App.Hr.Overtime", - "Order": 9, + "Order": 8, "Url": "/admin/list/list-overtime", "Icon": "FcClock", "RequiredPermissionName": "App.Hr.Overtime", @@ -2839,7 +2822,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Expense", "DisplayName": "App.Hr.Expense", - "Order": 10, + "Order": 9, "Url": "/admin/list/list-expense", "Icon": "FaFileInvoiceDollar", "RequiredPermissionName": "App.Hr.Expense", @@ -2849,7 +2832,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Payroll", "DisplayName": "App.Hr.Payroll", - "Order": 11, + "Order": 10, "Url": "/admin/list/list-payroll", "Icon": "FcMoneyTransfer", "RequiredPermissionName": "App.Hr.Payroll", @@ -2859,7 +2842,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Template360", "DisplayName": "App.Hr.Template360", - "Order": 12, + "Order": 11, "Url": "/admin/list/list-template360", "Icon": "FcInspection", "RequiredPermissionName": "App.Hr.Template360", @@ -2869,7 +2852,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Performance360", "DisplayName": "App.Hr.Performance360", - "Order": 13, + "Order": 12, "Url": "/admin/list/list-performance360", "Icon": "FcFeedback", "RequiredPermissionName": "App.Hr.Performance360", @@ -2879,7 +2862,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Training", "DisplayName": "App.Hr.Training", - "Order": 14, + "Order": 13, "Url": "/admin/list/list-training", "Icon": "FcGraduationCap", "RequiredPermissionName": "App.Hr.Training", @@ -2889,7 +2872,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Survey", "DisplayName": "App.Hr.Survey", - "Order": 15, + "Order": 14, "Url": "/admin/list/list-survey", "Icon": "FcSurvey", "RequiredPermissionName": "App.Hr.Survey", diff --git a/ui/src/views/hr/components/OrganizationChart.tsx b/ui/src/views/hr/components/OrganizationChart.tsx deleted file mode 100644 index 3ed8fdb9..00000000 --- a/ui/src/views/hr/components/OrganizationChart.tsx +++ /dev/null @@ -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(mockEmployees) - const [organizationData] = useState(generateOrganizationData()) - const [expandedNodes, setExpandedNodes] = useState>(new Set()) - const [orgTree, setOrgTree] = useState([]) - const [viewMode, setViewMode] = useState<'tree' | 'cards'>('tree') - const [selectedEmployee, setSelectedEmployee] = useState(null) - const [showModal, setShowModal] = useState(false) - const [selectedDepartment, setSelectedDepartment] = useState('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() - - // 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 ( -
- {/* Bağlantı çizgisi */} - {node.level > 0 && ( - - )} -
- {/* Girinti ve bağlantı çizgisi */} - {node.level > 0 && ( - - )} - - {/* Genişletme ikonu */} -
- {hasChildren && ( - - )} -
- - {/* Grup Bilgisi */} -
-
-
-
-

- {node.employee.fullName} -

-

- {node.employee.jobPosition?.name} -

-

{node.employee.department?.name}

-
-
-
-
-
- {hasChildren && ( - - - {node.children.length} - - )} - - -
-
-
-
- - {hasChildren && isExpanded && ( -
{node.children.map((child) => renderTreeNode(child))}
- )} -
- ) - } - - 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, - ) - - return ( -
- {Object.entries(groupedByLevel) - .sort(([a], [b]) => parseInt(a) - parseInt(b)) - .map(([level, levelEmployees]) => ( -
-

- - {level === '0' - ? 'Üst Yönetim' - : level === '1' - ? 'Orta Kademe Yönetim' - : level === '2' - ? 'Alt Kademe Yönetim' - : `${parseInt(level) + 1}. Seviye`} - - {levelEmployees.length} kişi - -

- -
- {levelEmployees.map((employee) => ( -
-
-
- -
- -
-

handleViewEmployee(employee)} - className="font-medium text-sm text-blue-600 hover:underline cursor-pointer truncate" - > - {employee.fullName} -

-

{employee.code}

-
-
- -
-

- {employee.jobPosition?.name} -

-

{employee.department?.name}

-
-
- ))} -
-
- ))} -
- ) - } - - return ( - -
- {/* Header */} -
-
-

Organizasyon Şeması

-

Kurumsal hiyerarşi ve raporlama yapısı

-
- -
- -
- - -
-
-
- - {/* Stats */} -
- - - e.department?.id).filter(Boolean)).size} - color="purple" - icon="FaBuilding" - /> - - 0 - ? Math.max(...organizationData.map((org) => org.level)) + 1 - : 0 - } - color="orange" - icon="FaUser" - /> -
- - {/* Content */} -
- {viewMode === 'tree' ? ( -
{filteredOrgTree.map((node) => renderTreeNode(node))}
- ) : ( - renderCardView() - )} - - {(viewMode === 'tree' - ? filteredOrgTree.length === 0 - : filteredEmployees.length === 0) && ( -
- -

- Organizasyon verisi bulunamadı -

-

Henüz organizasyon şeması oluşturulmamış.

-
- )} -
-
- - {/* Employee Detail Modal */} - {showModal && selectedEmployee && ( -
-
- {/* Modal Header */} -
-

Personel Detayları

- -
- - {/* Employee Info */} -
- {/* Basic Info */} -
-
- -
-
-

{selectedEmployee.fullName}

-

{selectedEmployee.code}

-

{selectedEmployee.jobPosition?.name}

-
-
- - {/* Contact Information */} -
-
- - İletişim Bilgileri -
-
-
- - E-posta: - {selectedEmployee.email} -
- {selectedEmployee.phoneNumber && ( -
- - İş Telefonu: - {selectedEmployee.phoneNumber} -
- )} - {selectedEmployee.mobileNumber && ( -
- - Kişisel Telefon: - - {selectedEmployee.mobileNumber} - -
- )} -
-
- - {/* Employment Information */} -
-
- - İş Bilgileri -
-
-
-
- Departman: -

{selectedEmployee.department?.name}

-
-
- Pozisyon: -

{selectedEmployee.jobPosition?.name}

-
-
- İşe Başlama Tarihi: -

- - {new Date(selectedEmployee.hireDate).toLocaleDateString('tr-TR')} -

-
-
- İstihdam Türü: -

{selectedEmployee.employmentType}

-
-
- Çalışma Lokasyonu: -

- - {selectedEmployee.workLocation} -

-
-
- Durum: - - {selectedEmployee.isActive ? 'Aktif' : 'Pasif'} - -
-
-
-
- - {/* Personal Information */} -
-
- - Kişisel Bilgiler -
-
-
-
- Doğum Tarihi: -

- - {new Date(selectedEmployee.birthDate).toLocaleDateString('tr-TR')} -

-
-
- Cinsiyet: -

{selectedEmployee.gender}

-
-
- Medeni Durum: -

{selectedEmployee.maritalStatus}

-
-
- TC Kimlik No: -

{selectedEmployee.nationalId}

-
-
- - {selectedEmployee.address && ( -
- Adres: -

- - {selectedEmployee.address.street}, {selectedEmployee.address.city},{' '} - {selectedEmployee.address.country} -

-
- )} -
-
- - {/* Emergency Contact */} - {selectedEmployee.emergencyContact && ( -
-
- - Acil Durum İletişim -
-
-
-
- İsim: -

{selectedEmployee.emergencyContact.name}

-
-
- Yakınlık: -

- {selectedEmployee.emergencyContact.relationship} -

-
-
- Telefon: -

{selectedEmployee.emergencyContact.phoneNumber}

-
-
-
-
- )} -
- - {/* Modal Footer */} -
- -
-
-
- )} -
- ) -} - -export default OrganizationChart diff --git a/ui/src/views/hr/components/PayrollManagement.tsx b/ui/src/views/hr/components/PayrollManagement.tsx deleted file mode 100644 index 78a56976..00000000 --- a/ui/src/views/hr/components/PayrollManagement.tsx +++ /dev/null @@ -1,1345 +0,0 @@ -import React, { useState } from 'react' -import { - FaCalculator, - FaFileAlt, - FaDownload, - FaEye, - FaEdit, - FaPlus, - FaTimes, - FaSave, - FaCheck, - FaBan, - FaUsers, -} from 'react-icons/fa' -import { HrPayroll, PayrollStatusEnum } from '../../../types/hr' -import DataTable, { Column } from '../../../components/common/DataTable' -import { mockEmployees } from '../../../mocks/mockEmployees' -import { mockPayrolls } from '../../../mocks/mockPayrolls' -import { mockDepartments } from '../../../mocks/mockDepartments' -import Widget from '../../../components/common/Widget' -import { getPayrollStatusColor, getPayrollStatusText } from '../../../utils/erp' -import { Container } from '@/components/shared' - -// Mock data - replace with actual data fetching - -const PayrollManagement: React.FC = () => { - const [payrolls, setPayrolls] = useState(mockPayrolls) - const [selectedStatus, setSelectedStatus] = useState('all') - const [selectedPeriod, setSelectedPeriod] = useState('all') - const [selectedDepartment, setSelectedDepartment] = useState('all') - const [showModal, setShowModal] = useState(false) - const [showBulkModal, setShowBulkModal] = useState(false) - const [modalType, setModalType] = useState<'add' | 'edit' | 'view'>('add') - const [selectedPayroll, setSelectedPayroll] = useState(null) - const [searchTerm, setSearchTerm] = useState('') - const [formData, setFormData] = useState>({ - period: '', - baseSalary: 0, - overtime: 0, - bonus: 0, - allowances: [], - deductions: [], - status: PayrollStatusEnum.Draft, - }) - - const handleAdd = () => { - setModalType('add') - setFormData({ - period: new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), - baseSalary: 0, - overtime: 0, - bonus: 0, - allowances: [], - deductions: [], - status: PayrollStatusEnum.Draft, - }) - setSelectedPayroll(null) - setShowModal(true) - } - - const handleEdit = (payroll: HrPayroll) => { - setModalType('edit') - setFormData(payroll) - setSelectedPayroll(payroll) - setShowModal(true) - } - - const handleView = (payroll: HrPayroll) => { - setModalType('view') - setFormData(payroll) - setSelectedPayroll(payroll) - setShowModal(true) - } - - const handleCalculate = (payroll: HrPayroll) => { - // Calculate gross and net salary - const totalAllowances = payroll.allowances.reduce( - (total, allowance) => total + allowance.amount, - 0, - ) - const totalDeductions = payroll.deductions.reduce( - (total, deduction) => total + deduction.amount, - 0, - ) - - const grossSalary = payroll.baseSalary + totalAllowances + payroll.overtime + payroll.bonus - const tax = grossSalary * 0.15 // 15% tax rate (simplified) - const socialSecurity = grossSalary * 0.14 // 14% social security (simplified) - const netSalary = grossSalary - tax - socialSecurity - totalDeductions - - const updatedPayroll: HrPayroll = { - ...payroll, - grossSalary, - tax, - socialSecurity, - netSalary, - status: PayrollStatusEnum.Calculated, - lastModificationTime: new Date(), - } - - setPayrolls((prevPayrolls) => - prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)), - ) - - alert( - `Bordro hesaplandı:\nBrüt Maaş: ₺${grossSalary.toLocaleString()}\nNet Maaş: ₺${netSalary.toLocaleString()}`, - ) - } - - const handleApprove = (payroll: HrPayroll) => { - const updatedPayroll: HrPayroll = { - ...payroll, - status: PayrollStatusEnum.Approved, - lastModificationTime: new Date(), - } - - setPayrolls((prevPayrolls) => - prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)), - ) - - alert(`Bordro onaylandı: ${payroll.employee?.fullName}`) - } - - const handleReject = (payroll: HrPayroll) => { - const updatedPayroll: HrPayroll = { - ...payroll, - status: PayrollStatusEnum.Cancelled, - lastModificationTime: new Date(), - } - - setPayrolls((prevPayrolls) => - prevPayrolls.map((p) => (p.id === payroll.id ? updatedPayroll : p)), - ) - - alert(`Bordro reddedildi: ${payroll.employee?.fullName}`) - } - - const handleBulkEntry = () => { - setShowBulkModal(true) - } - - const handleExport = (payroll: HrPayroll) => { - // Create a simple CSV export - const csvContent = [ - ['Personel Kodu', payroll.employee?.code || ''], - ['Personel Adı', payroll.employee?.fullName || ''], - ['Dönem', payroll.period], - ['Temel Maaş', `₺${payroll.baseSalary.toLocaleString()}`], - ['Mesai Ücreti', `₺${payroll.overtime.toLocaleString()}`], - ['Prim', `₺${payroll.bonus.toLocaleString()}`], - ['Brüt Maaş', `₺${payroll.grossSalary.toLocaleString()}`], - ['Vergi', `₺${payroll.tax.toLocaleString()}`], - ['SGK', `₺${payroll.socialSecurity.toLocaleString()}`], - ['Net Maaş', `₺${payroll.netSalary.toLocaleString()}`], - ['Durum', getPayrollStatusText(payroll.status)], - ] - .map((row) => row.join(',')) - .join('\n') - - const blob = new Blob(['\uFEFF' + csvContent], { - type: 'text/csv;charset=utf-8;', - }) - const link = document.createElement('a') - const url = URL.createObjectURL(blob) - link.setAttribute('href', url) - link.setAttribute('download', `bordro_${payroll.employee?.code}_${payroll.period}.csv`) - link.style.visibility = 'hidden' - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - } - - const handleSubmit = () => { - if (modalType === 'add') { - const newPayroll: HrPayroll = { - id: Date.now().toString(), - employeeId: formData.employeeId || '', - employee: mockEmployees.find((emp) => emp.id === formData.employeeId), - period: formData.period || '', - baseSalary: formData.baseSalary || 0, - allowances: formData.allowances || [], - deductions: formData.deductions || [], - overtime: formData.overtime || 0, - bonus: formData.bonus || 0, - grossSalary: 0, - netSalary: 0, - tax: 0, - socialSecurity: 0, - status: PayrollStatusEnum.Draft, - creationTime: new Date(), - lastModificationTime: new Date(), - } - - setPayrolls((prevPayrolls) => [...prevPayrolls, newPayroll]) - } else if (modalType === 'edit' && selectedPayroll) { - const updatedPayroll: HrPayroll = { - ...selectedPayroll, - ...formData, - lastModificationTime: new Date(), - } - - setPayrolls((prevPayrolls) => - prevPayrolls.map((p) => (p.id === selectedPayroll.id ? updatedPayroll : p)), - ) - } - - setShowModal(false) - setSelectedPayroll(null) - setFormData({ - period: '', - baseSalary: 0, - overtime: 0, - bonus: 0, - allowances: [], - deductions: [], - status: PayrollStatusEnum.Draft, - }) - } - - const handleCancel = () => { - setShowModal(false) - setSelectedPayroll(null) - setFormData({ - period: '', - baseSalary: 0, - overtime: 0, - bonus: 0, - allowances: [], - deductions: [], - status: PayrollStatusEnum.Draft, - }) - } - - // Calculate gross and net salary helper function - const calculateSalaries = (data: Partial) => { - const totalAllowances = (data.allowances || []).reduce( - (total, allowance) => total + allowance.amount, - 0, - ) - const totalDeductions = (data.deductions || []).reduce( - (total, deduction) => total + deduction.amount, - 0, - ) - - const grossSalary = - (data.baseSalary || 0) + totalAllowances + (data.overtime || 0) + (data.bonus || 0) - const tax = grossSalary * 0.15 // 15% tax rate (simplified) - const socialSecurity = grossSalary * 0.14 // 14% social security (simplified) - const netSalary = grossSalary - tax - socialSecurity - totalDeductions - - return { - grossSalary, - tax, - socialSecurity, - netSalary, - } - } - - // Real-time calculation for form data - const calculatedValues = calculateSalaries(formData) - - const filteredPayrolls = payrolls.filter((payroll) => { - if (selectedStatus !== 'all' && payroll.status !== selectedStatus) { - return false - } - if (selectedPeriod !== 'all' && payroll.period !== selectedPeriod) { - return false - } - if (selectedDepartment !== 'all' && payroll.employee?.department?.name !== selectedDepartment) { - return false - } - if (searchTerm.trim() !== '') { - if (payroll.employeeId !== searchTerm) { - return false - } - } - return true - }) - - const columns: Column[] = [ - { - key: 'employee', - header: 'Personel', - render: (payroll: HrPayroll) => ( -
-
{payroll.employee?.fullName}
-
{payroll.employee?.code}
-
- ), - }, - { - key: 'period', - header: 'Dönem', - render: (payroll: HrPayroll) => payroll.period, - }, - { - key: 'baseSalary', - header: 'Temel Maaş', - render: (payroll: HrPayroll) => `₺${payroll.baseSalary.toLocaleString()}`, - }, - { - key: 'allowances', - header: 'Ödemeler', - render: (payroll: HrPayroll) => { - const totalAllowances = payroll.allowances.reduce( - (total, allowance) => total + allowance.amount, - 0, - ) - return `₺${totalAllowances.toLocaleString()}` - }, - }, - { - key: 'deductions', - header: 'Kesintiler', - render: (payroll: HrPayroll) => { - const totalDeductions = payroll.deductions.reduce( - (total, deduction) => total + deduction.amount, - 0, - ) - return `₺${totalDeductions.toLocaleString()}` - }, - }, - { - key: 'grossSalary', - header: 'Brüt Maaş', - render: (payroll: HrPayroll) => ( -
₺{payroll.grossSalary.toLocaleString()}
- ), - }, - { - key: 'netSalary', - header: 'Net Maaş', - render: (payroll: HrPayroll) => ( -
₺{payroll.netSalary.toLocaleString()}
- ), - }, - { - key: 'status', - header: 'Durum', - render: (payroll: HrPayroll) => ( - - {getPayrollStatusText(payroll.status)} - - ), - }, - { - key: 'actions', - header: 'İşlemler', - render: (payroll: HrPayroll) => ( -
- - - {payroll.status === PayrollStatusEnum.Draft && ( - - )} - - {payroll.status === PayrollStatusEnum.Calculated && ( - <> - - - - )} - - - - {(payroll.status === PayrollStatusEnum.Approved || - payroll.status === PayrollStatusEnum.Paid) && ( - - )} -
- ), - }, - ] - - // Calculate statistics - const stats = { - total: payrolls.length, - totalGross: payrolls.reduce((total, p) => total + p.grossSalary, 0), - totalNet: payrolls.reduce((total, p) => total + p.netSalary, 0), - totalTax: payrolls.reduce((total, p) => total + p.tax, 0), - pending: payrolls.filter( - (p) => p.status === PayrollStatusEnum.Draft || p.status === PayrollStatusEnum.Calculated, - ).length, - } - - // Get unique periods for filter - const periods = [...new Set(payrolls.map((p) => p.period))].sort().reverse() - - return ( - -
- {/* Header */} -
-
-

Maaş, Prim, Bordro Yönetimi

-

Personel ödemelerini hesaplayın ve yönetin

-
-
- - -
-
- - {/* Stats Cards */} -
- - - - - - - - - -
- - {/* Filters */} -
- - - - - - - -
- - {/* Data Table */} -
- -
- - {filteredPayrolls.length === 0 && ( -
- -

Bordro bulunamadı

-

Seçilen kriterlere uygun bordro bulunmamaktadır.

-
- )} -
- - {/* Modal */} - {showModal && ( -
-
-
-

- {modalType === 'add' && 'Yeni Bordro Ekle'} - {modalType === 'edit' && 'Bordro Düzenle'} - {modalType === 'view' && 'Bordro Detayları'} -

- -
- -
- {/* Employee Selection */} - {modalType === 'add' && ( -
- - -
- )} - - {/* Employee Info (for edit/view) */} - {(modalType === 'edit' || modalType === 'view') && formData.employee && ( -
-
Personel
-
- {formData.employee.code} - {formData.employee.fullName} -
-
- )} - - {/* Period */} -
- - setFormData({ ...formData, period: e.target.value })} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - disabled={modalType === 'view'} - /> -
- - {/* Base Salary */} -
- - - setFormData({ - ...formData, - baseSalary: Number(e.target.value), - }) - } - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - disabled={modalType === 'view'} - /> -
- - {/* Overtime */} -
- - - setFormData({ - ...formData, - overtime: Number(e.target.value), - }) - } - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - disabled={modalType === 'view'} - /> -
- - {/* Bonus */} -
- - setFormData({ ...formData, bonus: Number(e.target.value) })} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - disabled={modalType === 'view'} - /> -
- - {/* Allowances */} - {modalType !== 'view' && ( -
-
- - -
- {formData.allowances?.map((allowance, index) => ( -
- { - const updatedAllowances = [...(formData.allowances || [])] - updatedAllowances[index] = { - ...allowance, - name: e.target.value, - } - setFormData({ - ...formData, - allowances: updatedAllowances, - }) - }} - className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - { - const updatedAllowances = [...(formData.allowances || [])] - updatedAllowances[index] = { - ...allowance, - amount: Number(e.target.value), - } - setFormData({ - ...formData, - allowances: updatedAllowances, - }) - }} - className="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - -
- ))} - {(!formData.allowances || formData.allowances.length === 0) && ( -

Henüz ödeme eklenmedi

- )} -
- )} - - {/* Deductions */} - {modalType !== 'view' && ( -
-
- - -
- {formData.deductions?.map((deduction, index) => ( -
- { - const updatedDeductions = [...(formData.deductions || [])] - updatedDeductions[index] = { - ...deduction, - name: e.target.value, - } - setFormData({ - ...formData, - deductions: updatedDeductions, - }) - }} - className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - { - const updatedDeductions = [...(formData.deductions || [])] - updatedDeductions[index] = { - ...deduction, - amount: Number(e.target.value), - } - setFormData({ - ...formData, - deductions: updatedDeductions, - }) - }} - className="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - -
- ))} - {(!formData.deductions || formData.deductions.length === 0) && ( -

Henüz kesinti eklenmedi

- )} -
- )} - - {/* Real-time calculation preview for add/edit modes */} - {modalType !== 'view' && ( -
-
Hesaplama Önizlemesi
-
-
-
Brüt Maaş
-
- ₺{calculatedValues.grossSalary.toLocaleString()} -
-
- Temel: ₺{(formData.baseSalary || 0).toLocaleString()} + Ödemeler: ₺ - {(formData.allowances || []) - .reduce((total, allowance) => total + allowance.amount, 0) - .toLocaleString()}{' '} - + Mesai: ₺{(formData.overtime || 0).toLocaleString()} + Prim: ₺ - {(formData.bonus || 0).toLocaleString()} -
-
-
-
Net Maaş
-
- ₺{calculatedValues.netSalary.toLocaleString()} -
-
- Brüt - Vergi: ₺{calculatedValues.tax.toLocaleString()} - SGK: ₺ - {calculatedValues.socialSecurity.toLocaleString()} - Kesintiler: ₺ - {(formData.deductions || []) - .reduce((total, deduction) => total + deduction.amount, 0) - .toLocaleString()} -
-
-
-
- )} - - {/* View mode - show allowances and deductions */} - {modalType === 'view' && ( - <> -
- -
- {formData.allowances && formData.allowances.length > 0 ? ( -
- {formData.allowances.map((allowance) => ( -
- {allowance.name} - ₺{allowance.amount.toLocaleString()} -
- ))} -
- Toplam: - - ₺ - {formData.allowances - .reduce((total, allowance) => total + allowance.amount, 0) - .toLocaleString()} - -
-
- ) : ( - Ödeme yok - )} -
-
- -
- -
- {formData.deductions && formData.deductions.length > 0 ? ( -
- {formData.deductions.map((deduction) => ( -
- {deduction.name} - ₺{deduction.amount.toLocaleString()} -
- ))} -
- Toplam: - - ₺ - {formData.deductions - .reduce((total, deduction) => total + deduction.amount, 0) - .toLocaleString()} - -
-
- ) : ( - Kesinti yok - )} -
-
- - )} - - {/* Calculated fields (show only in view/edit mode for existing payrolls) */} - {modalType === 'view' && ( - <> - {/* Status */} -
- -
- - {getPayrollStatusText(formData.status || PayrollStatusEnum.Draft)} - -
-
- -
-
- -
- ₺{formData.grossSalary?.toLocaleString()} -
-
-
- -
- ₺{formData.netSalary?.toLocaleString()} -
-
-
-
-
- -
- ₺{formData.tax?.toLocaleString()} -
-
-
- -
- ₺{formData.socialSecurity?.toLocaleString()} -
-
-
- - )} -
- - {/* Modal Actions */} - {modalType !== 'view' && ( -
- - -
- )} - - {modalType === 'view' && ( -
- -
- )} -
-
- )} - - {/* Bulk Entry Modal */} - {showBulkModal && ( - setShowBulkModal(false)} - onSubmit={(payrolls) => { - setPayrolls((prev) => [...prev, ...payrolls]) - setShowBulkModal(false) - }} - /> - )} -
- ) -} - -// Bulk Payroll Entry Component -interface BulkPayrollEntryProps { - onClose: () => void - onSubmit: (payrolls: HrPayroll[]) => void -} - -const BulkPayrollEntry: React.FC = ({ onClose, onSubmit }) => { - const [selectedDepartment, setSelectedDepartment] = useState('') - const [selectedEmployees, setSelectedEmployees] = useState([]) - const [period, setPeriod] = useState( - new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), - ) - const [bulkData, setBulkData] = useState({ - baseSalaryIncrease: 0, - overtimeHours: 0, - overtimeRate: 50, - bonusAmount: 0, - massBonus: 0, - }) - - // Get unique departments from mockDepartments and employees - const departments = [ - ...new Set([ - ...mockDepartments.map((dept) => dept.name), - ...mockEmployees.map((emp) => emp.department?.name).filter(Boolean), - ]), - ].sort() - - // Get employees by department - const filteredEmployees = selectedDepartment - ? mockEmployees.filter((emp) => emp.department?.name === selectedDepartment) - : [] - - // Get selected employees data - const selectedEmployeesData = filteredEmployees.filter((emp) => - selectedEmployees.includes(emp.id), - ) - - // Handle department change - reset selected employees and auto-select all - const handleDepartmentChange = (departmentName: string) => { - setSelectedDepartment(departmentName) - // Auto-select all employees when department is selected - if (departmentName) { - const employeesInDept = mockEmployees.filter((emp) => emp.department?.name === departmentName) - setSelectedEmployees(employeesInDept.map((emp) => emp.id)) - } else { - setSelectedEmployees([]) - } - } - - // Handle select all employees - const handleSelectAll = (checked: boolean) => { - if (checked) { - setSelectedEmployees(filteredEmployees.map((emp) => emp.id)) - } else { - setSelectedEmployees([]) - } - } - - // Handle individual employee selection - const handleEmployeeSelect = (employeeId: string, checked: boolean) => { - if (checked) { - setSelectedEmployees((prev) => [...prev, employeeId]) - } else { - setSelectedEmployees((prev) => prev.filter((id) => id !== employeeId)) - } - } - - const handleBulkSubmit = () => { - if (!selectedDepartment || selectedEmployeesData.length === 0) { - alert('Lütfen bir departman ve en az bir personel seçin') - return - } - - const newPayrolls: HrPayroll[] = selectedEmployeesData.map((employee) => { - const baseSalary = employee.baseSalary + bulkData.baseSalaryIncrease - const overtime = bulkData.overtimeHours * bulkData.overtimeRate - const bonus = bulkData.bonusAmount + bulkData.massBonus - - return { - id: `bulk_${Date.now()}_${employee.id}`, - employeeId: employee.id, - employee: employee, - period: period, - baseSalary: baseSalary, - allowances: [], - deductions: [], - overtime: overtime, - bonus: bonus, - grossSalary: 0, - netSalary: 0, - tax: 0, - socialSecurity: 0, - status: PayrollStatusEnum.Draft, - creationTime: new Date(), - lastModificationTime: new Date(), - } - }) - - onSubmit(newPayrolls) - } - - return ( -
-
-
-

Toplu Bordro Girişi

- -
- -
- {/* Department and Period */} -
-
- - -
-
- - setPeriod(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
-
- - {/* Bulk Settings */} -
-

Toplu Ayarlar

-
-
- - - setBulkData({ - ...bulkData, - baseSalaryIncrease: Number(e.target.value), - }) - } - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
-
- - - setBulkData({ - ...bulkData, - overtimeHours: Number(e.target.value), - }) - } - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
-
- - - setBulkData({ - ...bulkData, - overtimeRate: Number(e.target.value), - }) - } - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
-
- - - setBulkData({ - ...bulkData, - massBonus: Number(e.target.value), - }) - } - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
-
-
- - {/* Employee Selection */} - {selectedDepartment && filteredEmployees.length > 0 && ( -
-
-

- Personel Seçimi ({filteredEmployees.length} kişi) -

- -
- -
- - - - - - - - - - - - {filteredEmployees.map((employee) => ( - - - - - - - - ))} - -
- Seç - - Personel Kodu - - Ad Soyad - - Mevcut Maaş - - Yeni Maaş -
- handleEmployeeSelect(employee.id, e.target.checked)} - className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" - /> - {employee.code}{employee.fullName} - ₺{employee.baseSalary.toLocaleString()} - - ₺{(employee.baseSalary + bulkData.baseSalaryIncrease).toLocaleString()} -
-
- - {selectedEmployees.length > 0 && ( -
-

- {selectedEmployees.length} personel seçildi. Toplam mevcut - maaş:{' '} - - ₺ - {selectedEmployeesData - .reduce((total, emp) => total + emp.baseSalary, 0) - .toLocaleString()} - -

-
- )} -
- )} -
- - {/* Modal Actions */} -
- - -
-
-
- ) -} - -export default PayrollManagement