IK Management Container

This commit is contained in:
Sedat Öztürk 2025-09-15 22:46:52 +03:00
parent 4e5322ba0c
commit 0cb87b8295
16 changed files with 5683 additions and 7071 deletions

View file

@ -1,64 +1,55 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { FaAward, FaPlus, FaEdit, FaTrash, FaUsers } from "react-icons/fa"; import { FaAward, FaPlus, FaEdit, FaTrash, FaUsers } from 'react-icons/fa'
import { HrBadge, HrEmployeeBadge } from "../../../types/hr"; import { HrBadge, HrEmployeeBadge } from '../../../types/hr'
import DataTable, { Column } from "../../../components/common/DataTable"; import DataTable, { Column } from '../../../components/common/DataTable'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockBadges } from "../../../mocks/mockBadges"; import { mockBadges } from '../../../mocks/mockBadges'
import BadgeEditModal from "./BadgeEditModal"; import BadgeEditModal from './BadgeEditModal'
import BadgeAssignmentModal, { import BadgeAssignmentModal, { BadgeAssignmentFormData } from './BadgeAssignmentModal'
BadgeAssignmentFormData, import Widget from '../../../components/common/Widget'
} from "./BadgeAssignmentModal"; import { getIconComponent } from '../../../utils/erp'
import Widget from "../../../components/common/Widget"; import { Container } from '@/components/shared'
import { getIconComponent } from "../../../utils/erp";
const BadgeManagement: React.FC = () => { const BadgeManagement: React.FC = () => {
const [activeTab, setActiveTab] = useState<"badges" | "assignments">( const [activeTab, setActiveTab] = useState<'badges' | 'assignments'>('badges')
"badges" const [searchTerm, setSearchTerm] = useState('')
); const [searchAssignTerm, setSearchAssignTerm] = useState('')
const [searchTerm, setSearchTerm] = useState("");
const [searchAssignTerm, setSearchAssignTerm] = useState("");
// Modal states // Modal states
const [isBadgeModalOpen, setIsBadgeModalOpen] = useState(false); const [isBadgeModalOpen, setIsBadgeModalOpen] = useState(false)
const [isAssignmentModalOpen, setIsAssignmentModalOpen] = useState(false); const [isAssignmentModalOpen, setIsAssignmentModalOpen] = useState(false)
const [editingBadge, setEditingBadge] = useState<HrBadge | null>(null); const [editingBadge, setEditingBadge] = useState<HrBadge | null>(null)
// Mock data - In real app, this would come from API // Mock data - In real app, this would come from API
const [badges, setBadges] = useState(mockBadges); const [badges, setBadges] = useState(mockBadges)
const [employeeBadges, setEmployeeBadges] = useState<HrEmployeeBadge[]>([]); const [employeeBadges, setEmployeeBadges] = useState<HrEmployeeBadge[]>([])
// Handlers // Handlers
const handleAddBadge = () => { const handleAddBadge = () => {
setEditingBadge(null); setEditingBadge(null)
setIsBadgeModalOpen(true); setIsBadgeModalOpen(true)
}; }
const handleEditBadge = (badge: HrBadge) => { const handleEditBadge = (badge: HrBadge) => {
setEditingBadge(badge); setEditingBadge(badge)
setIsBadgeModalOpen(true); setIsBadgeModalOpen(true)
}; }
const handleDeleteBadge = (id: string) => { const handleDeleteBadge = (id: string) => {
if (window.confirm("Bu rozeti silmek istediğinizden emin misiniz?")) { if (window.confirm('Bu rozeti silmek istediğinizden emin misiniz?')) {
setBadges(badges.filter((badge) => badge.id !== id)); setBadges(badges.filter((badge) => badge.id !== id))
alert("Rozet başarıyla silindi!"); alert('Rozet başarıyla silindi!')
}
} }
};
const handleRevokeBadge = (employeeBadgeId: string) => { const handleRevokeBadge = (employeeBadgeId: string) => {
if ( if (window.confirm('Bu rozet atamasını iptal etmek istediğinizden emin misiniz?')) {
window.confirm(
"Bu rozet atamasını iptal etmek istediğinizden emin misiniz?"
)
) {
setEmployeeBadges((prev) => setEmployeeBadges((prev) =>
prev.map((eb) => prev.map((eb) => (eb.id === employeeBadgeId ? { ...eb, isActive: false } : eb)),
eb.id === employeeBadgeId ? { ...eb, isActive: false } : eb
) )
); alert('Rozet ataması iptal edildi!')
alert("Rozet ataması iptal edildi!"); }
} }
};
const handleBadgeSubmit = (badgeData: HrBadge) => { const handleBadgeSubmit = (badgeData: HrBadge) => {
if (editingBadge) { if (editingBadge) {
@ -71,29 +62,27 @@ const BadgeManagement: React.FC = () => {
...badgeData, ...badgeData,
lastModificationTime: new Date(), lastModificationTime: new Date(),
} }
: badge : badge,
),
) )
); alert('Rozet başarıyla güncellendi!')
alert("Rozet başarıyla güncellendi!");
} else { } else {
// Create new badge // Create new badge
const newBadge: HrBadge = { const newBadge: HrBadge = {
...badgeData, ...badgeData,
creationTime: new Date(), creationTime: new Date(),
lastModificationTime: new Date(), lastModificationTime: new Date(),
};
setBadges((prev) => [...prev, newBadge]);
alert("Yeni rozet başarıyla oluşturuldu!");
} }
setIsBadgeModalOpen(false); setBadges((prev) => [...prev, newBadge])
setEditingBadge(null); alert('Yeni rozet başarıyla oluşturuldu!')
}; }
setIsBadgeModalOpen(false)
setEditingBadge(null)
}
const handleAssignmentSubmit = (assignmentData: BadgeAssignmentFormData) => { const handleAssignmentSubmit = (assignmentData: BadgeAssignmentFormData) => {
const employee = mockEmployees.find( const employee = mockEmployees.find((emp) => emp.id === assignmentData.employeeId)
(emp) => emp.id === assignmentData.employeeId const badge = badges.find((b) => b.id === assignmentData.badgeId)
);
const badge = badges.find((b) => b.id === assignmentData.badgeId);
if (employee && badge) { if (employee && badge) {
const newAssignment: HrEmployeeBadge = { const newAssignment: HrEmployeeBadge = {
@ -103,42 +92,40 @@ const BadgeManagement: React.FC = () => {
badgeId: assignmentData.badgeId, badgeId: assignmentData.badgeId,
badge: badge, badge: badge,
earnedDate: new Date(assignmentData.earnedDate), earnedDate: new Date(assignmentData.earnedDate),
expiryDate: assignmentData.expiryDate expiryDate: assignmentData.expiryDate ? new Date(assignmentData.expiryDate) : undefined,
? new Date(assignmentData.expiryDate)
: undefined,
reason: assignmentData.reason, reason: assignmentData.reason,
notes: assignmentData.notes, notes: assignmentData.notes,
isActive: true, isActive: true,
creationTime: new Date(), creationTime: new Date(),
lastModificationTime: new Date(), lastModificationTime: new Date(),
};
setEmployeeBadges((prev) => [...prev, newAssignment]);
alert("Rozet başarıyla atandı!");
} }
setIsAssignmentModalOpen(false);
}; setEmployeeBadges((prev) => [...prev, newAssignment])
alert('Rozet başarıyla atandı!')
}
setIsAssignmentModalOpen(false)
}
const openAssignmentModal = () => { const openAssignmentModal = () => {
setIsAssignmentModalOpen(true); setIsAssignmentModalOpen(true)
}; }
const filteredBadges = badges.filter( const filteredBadges = badges.filter(
(badge) => (badge) =>
badge.name.toLowerCase().includes(searchTerm.toLowerCase()) || badge.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
badge.description.toLowerCase().includes(searchTerm.toLowerCase()) badge.description.toLowerCase().includes(searchTerm.toLowerCase()),
); )
const filteredEmployeeBadges = employeeBadges.filter((eb) => const filteredEmployeeBadges = employeeBadges.filter((eb) =>
eb.employee?.fullName.toLowerCase().includes(searchAssignTerm.toLowerCase()) eb.employee?.fullName.toLowerCase().includes(searchAssignTerm.toLowerCase()),
); )
const badgeColumns: Column<HrBadge>[] = [ const badgeColumns: Column<HrBadge>[] = [
{ {
key: "icon", key: 'icon',
header: "Simge", header: 'Simge',
render: (badge: HrBadge) => { render: (badge: HrBadge) => {
const IconComponent = getIconComponent(badge.icon); const IconComponent = getIconComponent(badge.icon)
return ( return (
<div <div
className={`w-7 h-7 rounded-full flex items-center justify-center`} className={`w-7 h-7 rounded-full flex items-center justify-center`}
@ -146,64 +133,56 @@ const BadgeManagement: React.FC = () => {
> >
<IconComponent className="w-3.5 h-3.5 text-white" /> <IconComponent className="w-3.5 h-3.5 text-white" />
</div> </div>
); )
}, },
}, },
{ {
key: "name", key: 'name',
header: "Rozet Adı", header: 'Rozet Adı',
sortable: true, sortable: true,
render: (badge: HrBadge) => ( render: (badge: HrBadge) => (
<div> <div>
<div className="font-medium text-gray-900">{badge.name}</div> <div className="font-medium text-gray-900">{badge.name}</div>
<div className="text-sm text-gray-500 truncate max-w-xs"> <div className="text-sm text-gray-500 truncate max-w-xs">{badge.description}</div>
{badge.description}
</div>
</div> </div>
), ),
}, },
{ {
key: "criteria", key: 'criteria',
header: "Kazanma Kriteri", header: 'Kazanma Kriteri',
render: (badge: HrBadge) => ( render: (badge: HrBadge) => (
<div className="text-sm text-gray-600 max-w-xs truncate"> <div className="text-sm text-gray-600 max-w-xs truncate">{badge.criteria}</div>
{badge.criteria}
</div>
), ),
}, },
{ {
key: "assignedCount", key: 'assignedCount',
header: "Atanan Sayısı", header: 'Atanan Sayısı',
render: (badge: HrBadge) => { render: (badge: HrBadge) => {
const count = employeeBadges.filter( const count = employeeBadges.filter((eb) => eb.badgeId === badge.id && eb.isActive).length
(eb) => eb.badgeId === badge.id && eb.isActive
).length;
return ( return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaUsers className="w-4 h-4 text-gray-500" /> <FaUsers className="w-4 h-4 text-gray-500" />
<span>{count}</span> <span>{count}</span>
</div> </div>
); )
}, },
}, },
{ {
key: "status", key: 'status',
header: "Durum", header: 'Durum',
render: (badge: HrBadge) => ( render: (badge: HrBadge) => (
<span <span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${ className={`px-2 py-0.5 text-xs font-medium rounded-full ${
badge.isActive badge.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`} }`}
> >
{badge.isActive ? "Aktif" : "Pasif"} {badge.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
), ),
}, },
{ {
key: "actions", key: 'actions',
header: "İşlemler", header: 'İşlemler',
render: (badge: HrBadge) => ( render: (badge: HrBadge) => (
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
@ -223,30 +202,24 @@ const BadgeManagement: React.FC = () => {
</div> </div>
), ),
}, },
]; ]
const assignmentColumns: Column<HrEmployeeBadge>[] = [ const assignmentColumns: Column<HrEmployeeBadge>[] = [
{ {
key: "employee", key: 'employee',
header: "Personel", header: 'Personel',
render: (assignment: HrEmployeeBadge) => ( render: (assignment: HrEmployeeBadge) => (
<div> <div>
<div className="font-medium text-gray-900"> <div className="font-medium text-gray-900">{assignment.employee?.fullName}</div>
{assignment.employee?.fullName} <div className="text-sm text-gray-500">{assignment.employee?.code}</div>
</div>
<div className="text-sm text-gray-500">
{assignment.employee?.code}
</div>
</div> </div>
), ),
}, },
{ {
key: "badge", key: 'badge',
header: "Rozet", header: 'Rozet',
render: (assignment: HrEmployeeBadge) => { render: (assignment: HrEmployeeBadge) => {
const IconComponent = getIconComponent( const IconComponent = getIconComponent(assignment.badge?.icon || 'award')
assignment.badge?.icon || "award"
);
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
@ -257,51 +230,46 @@ const BadgeManagement: React.FC = () => {
</div> </div>
<span>{assignment.badge?.name}</span> <span>{assignment.badge?.name}</span>
</div> </div>
); )
}, },
}, },
{ {
key: "earnedDate", key: 'earnedDate',
header: "Kazanıldığı Tarih", header: 'Kazanıldığı Tarih',
render: (assignment: HrEmployeeBadge) => render: (assignment: HrEmployeeBadge) =>
new Date(assignment.earnedDate).toLocaleDateString("tr-TR"), new Date(assignment.earnedDate).toLocaleDateString('tr-TR'),
}, },
{ {
key: "expiryDate", key: 'expiryDate',
header: "Geçerlilik Süresi", header: 'Geçerlilik Süresi',
render: (assignment: HrEmployeeBadge) => render: (assignment: HrEmployeeBadge) =>
assignment.expiryDate assignment.expiryDate
? new Date(assignment.expiryDate).toLocaleDateString("tr-TR") ? new Date(assignment.expiryDate).toLocaleDateString('tr-TR')
: "Süresiz", : 'Süresiz',
}, },
{ {
key: "status", key: 'status',
header: "Durum", header: 'Durum',
render: (assignment: HrEmployeeBadge) => { render: (assignment: HrEmployeeBadge) => {
const isExpired = const isExpired = assignment.expiryDate && new Date(assignment.expiryDate) < new Date()
assignment.expiryDate && new Date(assignment.expiryDate) < new Date();
return ( return (
<span <span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${ className={`px-2 py-0.5 text-xs font-medium rounded-full ${
!assignment.isActive !assignment.isActive
? "bg-red-100 text-red-800" ? 'bg-red-100 text-red-800'
: isExpired : isExpired
? "bg-yellow-100 text-yellow-800" ? 'bg-yellow-100 text-yellow-800'
: "bg-green-100 text-green-800" : 'bg-green-100 text-green-800'
}`} }`}
> >
{!assignment.isActive {!assignment.isActive ? 'İptal Edildi' : isExpired ? 'Süresi Dolmuş' : 'Aktif'}
? "İptal Edildi"
: isExpired
? "Süresi Dolmuş"
: "Aktif"}
</span> </span>
); )
}, },
}, },
{ {
key: "actions", key: 'actions',
header: "İşlemler", header: 'İşlemler',
render: (assignment: HrEmployeeBadge) => ( render: (assignment: HrEmployeeBadge) => (
<div className="flex gap-2"> <div className="flex gap-2">
{assignment.isActive && ( {assignment.isActive && (
@ -316,10 +284,11 @@ const BadgeManagement: React.FC = () => {
</div> </div>
), ),
}, },
]; ]
return ( return (
<div className="space-y-3 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div> <div>
@ -332,12 +301,7 @@ const BadgeManagement: React.FC = () => {
{/* Stats Cards */} {/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3"> <div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<Widget <Widget title="Toplam Rozet" value={badges.length} color="blue" icon="FaAward" />
title="Toplam Rozet"
value={badges.length}
color="blue"
icon="FaAward"
/>
<Widget <Widget
title="Aktif Rozet" title="Aktif Rozet"
@ -355,9 +319,7 @@ const BadgeManagement: React.FC = () => {
<Widget <Widget
title="Rozetli Personel" title="Rozetli Personel"
value={ value={new Set(filteredEmployeeBadges.map((ab) => ab.employeeId)).size}
new Set(filteredEmployeeBadges.map((ab) => ab.employeeId)).size
}
color="purple" color="purple"
icon="FaStar" icon="FaStar"
/> />
@ -367,21 +329,21 @@ const BadgeManagement: React.FC = () => {
<div className="border-b border-gray-200"> <div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8"> <nav className="-mb-px flex space-x-8">
<button <button
onClick={() => setActiveTab("badges")} onClick={() => setActiveTab('badges')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${ className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === "badges" activeTab === 'badges'
? "border-blue-500 text-blue-600" ? 'border-blue-500 text-blue-600'
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`} }`}
> >
Rozetler Rozetler
</button> </button>
<button <button
onClick={() => setActiveTab("assignments")} onClick={() => setActiveTab('assignments')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${ className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === "assignments" activeTab === 'assignments'
? "border-blue-500 text-blue-600" ? 'border-blue-500 text-blue-600'
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`} }`}
> >
Rozet Atamaları Rozet Atamaları
@ -390,7 +352,7 @@ const BadgeManagement: React.FC = () => {
</div> </div>
{/* Tab Content */} {/* Tab Content */}
{activeTab === "badges" && ( {activeTab === 'badges' && (
<div className="space-y-3 pt-2"> <div className="space-y-3 pt-2">
{/* Search */} {/* Search */}
@ -419,7 +381,7 @@ const BadgeManagement: React.FC = () => {
</div> </div>
)} )}
{activeTab === "assignments" && ( {activeTab === 'assignments' && (
<div className="space-y-3 pt-2"> <div className="space-y-3 pt-2">
<div className="bg-white p-3 rounded-lg shadow-sm border"> <div className="bg-white p-3 rounded-lg shadow-sm border">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
@ -442,30 +404,25 @@ const BadgeManagement: React.FC = () => {
{/* Assignments Table */} {/* Assignments Table */}
<div className="bg-white rounded-lg shadow-sm border"> <div className="bg-white rounded-lg shadow-sm border">
<DataTable <DataTable data={filteredEmployeeBadges} columns={assignmentColumns} />
data={filteredEmployeeBadges}
columns={assignmentColumns}
/>
</div> </div>
</div> </div>
)} )}
{(activeTab === "badges" ? filteredBadges : filteredEmployeeBadges) {(activeTab === 'badges' ? filteredBadges : filteredEmployeeBadges).length === 0 && (
.length === 0 && (
<div className="text-center py-12"> <div className="text-center py-12">
<FaAward className="w-10 h-10 text-gray-400 mx-auto mb-3" /> <FaAward className="w-10 h-10 text-gray-400 mx-auto mb-3" />
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">
{activeTab === "badges" {activeTab === 'badges' ? 'Rozet bulunamadı' : 'Rozet ataması bulunamadı'}
? "Rozet bulunamadı"
: "Rozet ataması bulunamadı"}
</h3> </h3>
<p className="text-gray-500"> <p className="text-gray-500">
{activeTab === "badges" {activeTab === 'badges'
? "Henüz rozet tanımlanmamış." ? 'Henüz rozet tanımlanmamış.'
: "Henüz rozet ataması yapılmamış."} : 'Henüz rozet ataması yapılmamış.'}
</p> </p>
</div> </div>
)} )}
</div>
{/* Badge Edit Modal */} {/* Badge Edit Modal */}
<BadgeEditModal <BadgeEditModal
@ -473,7 +430,7 @@ const BadgeManagement: React.FC = () => {
onClose={() => setIsBadgeModalOpen(false)} onClose={() => setIsBadgeModalOpen(false)}
badge={editingBadge} badge={editingBadge}
onSubmit={handleBadgeSubmit} onSubmit={handleBadgeSubmit}
mode={editingBadge ? "edit" : "create"} mode={editingBadge ? 'edit' : 'create'}
/> />
{/* Badge Assignment Modal */} {/* Badge Assignment Modal */}
@ -482,8 +439,8 @@ const BadgeManagement: React.FC = () => {
onClose={() => setIsAssignmentModalOpen(false)} onClose={() => setIsAssignmentModalOpen(false)}
onSubmit={handleAssignmentSubmit} onSubmit={handleAssignmentSubmit}
/> />
</div> </Container>
); )
}; }
export default BadgeManagement; export default BadgeManagement

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import {
FaPlus, FaPlus,
FaEdit, FaEdit,
@ -9,64 +9,54 @@ import {
FaEye, FaEye,
FaList, FaList,
FaTh, FaTh,
} from "react-icons/fa"; } from 'react-icons/fa'
import { HrCostCenter, CostCenterType } from "../../../types/hr"; import { HrCostCenter, CostCenterType } from '../../../types/hr'
import DataTable, { Column } from "../../../components/common/DataTable"; import DataTable, { Column } from '../../../components/common/DataTable'
import { mockCostCenters } from "../../../mocks/mockCostCenters"; import { mockCostCenters } from '../../../mocks/mockCostCenters'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import CostCenterFormModal from "./CostCenterFormModal"; import CostCenterFormModal from './CostCenterFormModal'
import CostCenterViewModal from "./CostCenterViewModal"; import CostCenterViewModal from './CostCenterViewModal'
import Widget from "../../../components/common/Widget"; import Widget from '../../../components/common/Widget'
import { import { getCostCenterTypeColor, getCostCenterTypeText } from '../../../utils/erp'
getCostCenterTypeColor, import { Container } from '@/components/shared'
getCostCenterTypeText,
} from "../../../utils/erp";
const CostCenterManagement: React.FC = () => { const CostCenterManagement: React.FC = () => {
const [costCenters, setCostCenters] = const [costCenters, setCostCenters] = useState<HrCostCenter[]>(mockCostCenters)
useState<HrCostCenter[]>(mockCostCenters); const [searchTerm, setSearchTerm] = useState('')
const [searchTerm, setSearchTerm] = useState(""); const [selectedType, setSelectedType] = useState<string>('all')
const [selectedType, setSelectedType] = useState<string>("all"); const [viewMode, setViewMode] = useState<'list' | 'cards'>('list')
const [viewMode, setViewMode] = useState<"list" | "cards">("list"); const [isFormModalOpen, setIsFormModalOpen] = useState(false)
const [isFormModalOpen, setIsFormModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false)
const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [selectedCostCenter, setSelectedCostCenter] = useState<HrCostCenter | null>(null)
const [selectedCostCenter, setSelectedCostCenter] = const [editingCostCenter, setEditingCostCenter] = useState<HrCostCenter | undefined>(undefined)
useState<HrCostCenter | null>(null);
const [editingCostCenter, setEditingCostCenter] = useState<
HrCostCenter | undefined
>(undefined);
// Cost center bağlantılarını kur // Cost center bağlantılarını kur
mockCostCenters.forEach((cc) => { mockCostCenters.forEach((cc) => {
if (cc.responsibleEmployeeId) { if (cc.responsibleEmployeeId) {
cc.responsibleEmployee = mockEmployees.find( cc.responsibleEmployee = mockEmployees.find((emp) => emp.id === cc.responsibleEmployeeId)
(emp) => emp.id === cc.responsibleEmployeeId
);
} }
}); })
const handleAdd = () => { const handleAdd = () => {
setEditingCostCenter(undefined); setEditingCostCenter(undefined)
setIsFormModalOpen(true); setIsFormModalOpen(true)
}; }
const handleEdit = (costCenter: HrCostCenter) => { const handleEdit = (costCenter: HrCostCenter) => {
setEditingCostCenter(costCenter); setEditingCostCenter(costCenter)
setIsFormModalOpen(true); setIsFormModalOpen(true)
}; }
const handleView = (costCenter: HrCostCenter) => { const handleView = (costCenter: HrCostCenter) => {
setSelectedCostCenter(costCenter); setSelectedCostCenter(costCenter)
setIsViewModalOpen(true); setIsViewModalOpen(true)
}; }
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
if ( if (window.confirm('Bu maliyet merkezini silmek istediğinizden emin misiniz?')) {
window.confirm("Bu maliyet merkezini silmek istediğinizden emin misiniz?") setCostCenters(costCenters.filter((cc) => cc.id !== id))
) { }
setCostCenters(costCenters.filter((cc) => cc.id !== id));
} }
};
const handleSave = (costCenterData: Partial<HrCostCenter>) => { const handleSave = (costCenterData: Partial<HrCostCenter>) => {
if (editingCostCenter) { if (editingCostCenter) {
@ -79,9 +69,9 @@ const CostCenterManagement: React.FC = () => {
...costCenterData, ...costCenterData,
lastModificationTime: new Date(), lastModificationTime: new Date(),
} }
: cc : cc,
),
) )
);
} else { } else {
// Add new // Add new
const newCostCenter: HrCostCenter = { const newCostCenter: HrCostCenter = {
@ -90,12 +80,12 @@ const CostCenterManagement: React.FC = () => {
subCostCenters: [], subCostCenters: [],
creationTime: new Date(), creationTime: new Date(),
lastModificationTime: new Date(), lastModificationTime: new Date(),
} as HrCostCenter; } as HrCostCenter
setCostCenters([...costCenters, newCostCenter]); setCostCenters([...costCenters, newCostCenter])
}
setIsFormModalOpen(false)
setEditingCostCenter(undefined)
} }
setIsFormModalOpen(false);
setEditingCostCenter(undefined);
};
const filteredCostCenters = costCenters.filter((costCenter) => { const filteredCostCenters = costCenters.filter((costCenter) => {
if ( if (
@ -103,37 +93,37 @@ const CostCenterManagement: React.FC = () => {
!costCenter.name.toLowerCase().includes(searchTerm.toLowerCase()) && !costCenter.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
!costCenter.code.toLowerCase().includes(searchTerm.toLowerCase()) !costCenter.code.toLowerCase().includes(searchTerm.toLowerCase())
) { ) {
return false; return false
} }
if (selectedType !== "all" && costCenter.costCenterType !== selectedType) { if (selectedType !== 'all' && costCenter.costCenterType !== selectedType) {
return false; return false
} }
return true; return true
}); })
const getVariancePercentage = (budgeted: number, actual: number): number => { const getVariancePercentage = (budgeted: number, actual: number): number => {
if (budgeted === 0) return 0; if (budgeted === 0) return 0
return ((actual - budgeted) / budgeted) * 100; return ((actual - budgeted) / budgeted) * 100
}; }
const columns: Column<HrCostCenter>[] = [ const columns: Column<HrCostCenter>[] = [
{ {
key: "code", key: 'code',
header: "Maliyet Merkezi Kodu", header: 'Maliyet Merkezi Kodu',
sortable: true, sortable: true,
}, },
{ {
key: "name", key: 'name',
header: "Maliyet Merkezi Adı", header: 'Maliyet Merkezi Adı',
sortable: true, sortable: true,
}, },
{ {
key: "costCenterType", key: 'costCenterType',
header: "Tip", header: 'Tip',
render: (costCenter: HrCostCenter) => ( render: (costCenter: HrCostCenter) => (
<span <span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${getCostCenterTypeColor( className={`px-2 py-0.5 text-xs font-medium rounded-full ${getCostCenterTypeColor(
costCenter.costCenterType costCenter.costCenterType,
)}`} )}`}
> >
{getCostCenterTypeText(costCenter.costCenterType)} {getCostCenterTypeText(costCenter.costCenterType)}
@ -141,14 +131,13 @@ const CostCenterManagement: React.FC = () => {
), ),
}, },
{ {
key: "responsibleEmployee", key: 'responsibleEmployee',
header: "Sorumlu", header: 'Sorumlu',
render: (costCenter: HrCostCenter) => render: (costCenter: HrCostCenter) => costCenter.responsibleEmployee?.fullName || '-',
costCenter.responsibleEmployee?.fullName || "-",
}, },
{ {
key: "budgetedAmount", key: 'budgetedAmount',
header: "Bütçe", header: 'Bütçe',
render: (costCenter: HrCostCenter) => ( render: (costCenter: HrCostCenter) => (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaDollarSign className="w-4 h-4 text-gray-500" /> <FaDollarSign className="w-4 h-4 text-gray-500" />
@ -157,8 +146,8 @@ const CostCenterManagement: React.FC = () => {
), ),
}, },
{ {
key: "actualAmount", key: 'actualAmount',
header: "Gerçekleşen", header: 'Gerçekleşen',
render: (costCenter: HrCostCenter) => ( render: (costCenter: HrCostCenter) => (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaDollarSign className="w-4 h-4 text-gray-500" /> <FaDollarSign className="w-4 h-4 text-gray-500" />
@ -167,47 +156,38 @@ const CostCenterManagement: React.FC = () => {
), ),
}, },
{ {
key: "variance", key: 'variance',
header: "Fark (%)", header: 'Fark (%)',
render: (costCenter: HrCostCenter) => { render: (costCenter: HrCostCenter) => {
const variance = getVariancePercentage( const variance = getVariancePercentage(costCenter.budgetedAmount, costCenter.actualAmount)
costCenter.budgetedAmount, const isPositive = variance > 0
costCenter.actualAmount
);
const isPositive = variance > 0;
return ( return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaPercentage className="w-4 h-4 text-gray-500" /> <FaPercentage className="w-4 h-4 text-gray-500" />
<span <span className={`font-medium ${isPositive ? 'text-red-600' : 'text-green-600'}`}>
className={`font-medium ${ {isPositive ? '+' : ''}
isPositive ? "text-red-600" : "text-green-600"
}`}
>
{isPositive ? "+" : ""}
{variance.toFixed(1)}% {variance.toFixed(1)}%
</span> </span>
</div> </div>
); )
}, },
}, },
{ {
key: "status", key: 'status',
header: "Durum", header: 'Durum',
render: (costCenter: HrCostCenter) => ( render: (costCenter: HrCostCenter) => (
<span <span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${ className={`px-2 py-0.5 text-xs font-medium rounded-full ${
costCenter.isActive costCenter.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`} }`}
> >
{costCenter.isActive ? "Aktif" : "Pasif"} {costCenter.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
), ),
}, },
{ {
key: "actions", key: 'actions',
header: "İşlemler", header: 'İşlemler',
render: (costCenter: HrCostCenter) => ( render: (costCenter: HrCostCenter) => (
<div className="flex gap-1"> <div className="flex gap-1">
<button <button
@ -234,23 +214,17 @@ const CostCenterManagement: React.FC = () => {
</div> </div>
), ),
}, },
]; ]
const totalBudget = costCenters.reduce( const totalBudget = costCenters.reduce((sum, cc) => sum + cc.budgetedAmount, 0)
(sum, cc) => sum + cc.budgetedAmount, const totalActual = costCenters.reduce((sum, cc) => sum + cc.actualAmount, 0)
0 const totalVariance = getVariancePercentage(totalBudget, totalActual)
);
const totalActual = costCenters.reduce((sum, cc) => sum + cc.actualAmount, 0);
const totalVariance = getVariancePercentage(totalBudget, totalActual);
const renderCards = () => ( const renderCards = () => (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{filteredCostCenters.map((costCenter) => { {filteredCostCenters.map((costCenter) => {
const variance = getVariancePercentage( const variance = getVariancePercentage(costCenter.budgetedAmount, costCenter.actualAmount)
costCenter.budgetedAmount, const isPositive = variance > 0
costCenter.actualAmount
);
const isPositive = variance > 0;
return ( return (
<div <div
@ -260,14 +234,12 @@ const CostCenterManagement: React.FC = () => {
<div className="p-4"> <div className="p-4">
<div className="flex items-start justify-between mb-4"> <div className="flex items-start justify-between mb-4">
<div> <div>
<h3 className="text-lg font-semibold text-gray-900 mb-1"> <h3 className="text-lg font-semibold text-gray-900 mb-1">{costCenter.name}</h3>
{costCenter.name}
</h3>
<p className="text-sm text-gray-600">{costCenter.code}</p> <p className="text-sm text-gray-600">{costCenter.code}</p>
</div> </div>
<span <span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${getCostCenterTypeColor( className={`px-2 py-0.5 text-xs font-medium rounded-full ${getCostCenterTypeColor(
costCenter.costCenterType costCenter.costCenterType,
)}`} )}`}
> >
{getCostCenterTypeText(costCenter.costCenterType)} {getCostCenterTypeText(costCenter.costCenterType)}
@ -275,32 +247,22 @@ const CostCenterManagement: React.FC = () => {
</div> </div>
{costCenter.description && ( {costCenter.description && (
<p className="text-sm text-gray-600 mb-4 line-clamp-2"> <p className="text-sm text-gray-600 mb-4 line-clamp-2">{costCenter.description}</p>
{costCenter.description}
</p>
)} )}
<div className="space-y-3 mb-4"> <div className="space-y-3 mb-4">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-sm text-gray-600">Bütçe:</span> <span className="text-sm text-gray-600">Bütçe:</span>
<span className="font-medium"> <span className="font-medium">{costCenter.budgetedAmount.toLocaleString()}</span>
{costCenter.budgetedAmount.toLocaleString()}
</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-sm text-gray-600">Gerçekleşen:</span> <span className="text-sm text-gray-600">Gerçekleşen:</span>
<span className="font-medium"> <span className="font-medium">{costCenter.actualAmount.toLocaleString()}</span>
{costCenter.actualAmount.toLocaleString()}
</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-sm text-gray-600">Fark:</span> <span className="text-sm text-gray-600">Fark:</span>
<span <span className={`font-medium ${isPositive ? 'text-red-600' : 'text-green-600'}`}>
className={`font-medium ${ {isPositive ? '+' : ''}
isPositive ? "text-red-600" : "text-green-600"
}`}
>
{isPositive ? "+" : ""}
{variance.toFixed(1)}% {variance.toFixed(1)}%
</span> </span>
</div> </div>
@ -318,12 +280,10 @@ const CostCenterManagement: React.FC = () => {
<div className="flex items-center justify-between pt-4 border-t"> <div className="flex items-center justify-between pt-4 border-t">
<span <span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${ className={`px-2 py-0.5 text-xs font-medium rounded-full ${
costCenter.isActive costCenter.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`} }`}
> >
{costCenter.isActive ? "Aktif" : "Pasif"} {costCenter.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
<div className="flex gap-1"> <div className="flex gap-1">
@ -352,19 +312,18 @@ const CostCenterManagement: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
); )
})} })}
</div> </div>
); )
return ( return (
<div className="space-y-3 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div> <div>
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">Maliyet Merkezi Yönetimi</h2>
Maliyet Merkezi Yönetimi
</h2>
<p className="text-sm text-gray-600 mt-1"> <p className="text-sm text-gray-600 mt-1">
Maliyet merkezlerini ve bütçe takibini yönetin Maliyet merkezlerini ve bütçe takibini yönetin
</p> </p>
@ -403,8 +362,8 @@ const CostCenterManagement: React.FC = () => {
<Widget <Widget
title="Toplam Fark" title="Toplam Fark"
value={`${totalVariance > 0 ? "+" : ""}${totalVariance.toFixed(1)}%`} value={`${totalVariance > 0 ? '+' : ''}${totalVariance.toFixed(1)}%`}
color={totalVariance > 0 ? "red" : "green"} color={totalVariance > 0 ? 'red' : 'green'}
icon="FaPercentage" icon="FaPercentage"
/> />
</div> </div>
@ -439,22 +398,22 @@ const CostCenterManagement: React.FC = () => {
{/* View Mode Toggle */} {/* View Mode Toggle */}
<div className="flex items-center gap-2 bg-gray-100 rounded-lg p-1"> <div className="flex items-center gap-2 bg-gray-100 rounded-lg p-1">
<button <button
onClick={() => setViewMode("list")} onClick={() => setViewMode('list')}
className={`p-1.5 rounded-md text-sm transition-colors ${ className={`p-1.5 rounded-md text-sm transition-colors ${
viewMode === "list" viewMode === 'list'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
title="Liste Görünümü" title="Liste Görünümü"
> >
<FaList className="w-4 h-4" /> <FaList className="w-4 h-4" />
</button> </button>
<button <button
onClick={() => setViewMode("cards")} onClick={() => setViewMode('cards')}
className={`p-1.5 rounded-md text-sm transition-colors ${ className={`p-1.5 rounded-md text-sm transition-colors ${
viewMode === "cards" viewMode === 'cards'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
title="Kart Görünümü" title="Kart Görünümü"
> >
@ -464,7 +423,7 @@ const CostCenterManagement: React.FC = () => {
</div> </div>
{/* Content */} {/* Content */}
{viewMode === "list" ? ( {viewMode === 'list' ? (
<div className="bg-white rounded-lg shadow-sm border"> <div className="bg-white rounded-lg shadow-sm border">
<DataTable data={filteredCostCenters} columns={columns} /> <DataTable data={filteredCostCenters} columns={columns} />
</div> </div>
@ -475,39 +434,34 @@ const CostCenterManagement: React.FC = () => {
{filteredCostCenters.length === 0 && ( {filteredCostCenters.length === 0 && (
<div className="text-center py-12"> <div className="text-center py-12">
<FaChartPie className="w-10 h-10 text-gray-400 mx-auto mb-3" /> <FaChartPie className="w-10 h-10 text-gray-400 mx-auto mb-3" />
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">Maliyet merkezi bulunamadı</h3>
Maliyet merkezi bulunamadı <p className="text-gray-500">Arama kriterlerinizi değiştirmeyi deneyin.</p>
</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirmeyi deneyin.
</p>
</div> </div>
)} )}
</div>
{/* Modals */} {/* Modals */}
<CostCenterFormModal <CostCenterFormModal
isOpen={isFormModalOpen} isOpen={isFormModalOpen}
onClose={() => { onClose={() => {
setIsFormModalOpen(false); setIsFormModalOpen(false)
setEditingCostCenter(undefined); setEditingCostCenter(undefined)
}} }}
onSave={handleSave} onSave={handleSave}
costCenter={editingCostCenter} costCenter={editingCostCenter}
title={ title={editingCostCenter ? 'Maliyet Merkezi Düzenle' : 'Yeni Maliyet Merkezi'}
editingCostCenter ? "Maliyet Merkezi Düzenle" : "Yeni Maliyet Merkezi"
}
/> />
<CostCenterViewModal <CostCenterViewModal
isOpen={isViewModalOpen} isOpen={isViewModalOpen}
onClose={() => { onClose={() => {
setIsViewModalOpen(false); setIsViewModalOpen(false)
setSelectedCostCenter(null); setSelectedCostCenter(null)
}} }}
costCenter={selectedCostCenter} costCenter={selectedCostCenter}
/> />
</div> </Container>
); )
}; }
export default CostCenterManagement; export default CostCenterManagement

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,5 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import { FaEye, FaEdit, FaPlus, FaTimes, FaSave, FaTrash, FaQuestionCircle } from 'react-icons/fa'
FaEye,
FaEdit,
FaPlus,
FaTimes,
FaSave,
FaTrash,
FaQuestionCircle,
} from "react-icons/fa";
import { import {
HrEvaluation360Template, HrEvaluation360Template,
HrQuestionGroup, HrQuestionGroup,
@ -15,77 +7,73 @@ import {
QuestionTypeEnum, QuestionTypeEnum,
HrQuestionOption, HrQuestionOption,
AssessorTypeEnum, AssessorTypeEnum,
} from "../../../types/hr"; } from '../../../types/hr'
import DataTable, { Column } from "../../../components/common/DataTable"; import DataTable, { Column } from '../../../components/common/DataTable'
import { mockEvaluation360Templates } from "../../../mocks/mockEvaluation360Templates"; import { mockEvaluation360Templates } from '../../../mocks/mockEvaluation360Templates'
import Widget from "../../../components/common/Widget"; import Widget from '../../../components/common/Widget'
import { Container } from '@/components/shared'
const Degree360Templates: React.FC = () => { const Degree360Templates: React.FC = () => {
const [templateSearchTerm, setTemplateSearchTerm] = useState(""); const [templateSearchTerm, setTemplateSearchTerm] = useState('')
// Modal states // Modal states
const [showTemplateModal, setShowTemplateModal] = useState(false); const [showTemplateModal, setShowTemplateModal] = useState(false)
const [showQuestionModal, setShowQuestionModal] = useState(false); const [showQuestionModal, setShowQuestionModal] = useState(false)
const [showQuestionItemModal, setShowQuestionItemModal] = useState(false); const [showQuestionItemModal, setShowQuestionItemModal] = useState(false)
const [selectedTemplate, setSelectedTemplate] = const [selectedTemplate, setSelectedTemplate] = useState<HrEvaluation360Template | null>(null)
useState<HrEvaluation360Template | null>(null); const [selectedQuestionGroup, setSelectedQuestionGroup] = useState<HrQuestionGroup | null>(null)
const [selectedQuestionGroup, setSelectedQuestionGroup] = const [selectedQuestion, setSelectedQuestion] = useState<HrEvaluationQuestion | null>(null)
useState<HrQuestionGroup | null>(null); const [isEditMode, setIsEditMode] = useState(false)
const [selectedQuestion, setSelectedQuestion] =
useState<HrEvaluationQuestion | null>(null);
const [isEditMode, setIsEditMode] = useState(false);
// Form states // Form states
const [templateForm, setTemplateForm] = useState({ const [templateForm, setTemplateForm] = useState({
name: "", name: '',
description: "", description: '',
isActive: true, isActive: true,
assessorTypes: [] as AssessorTypeEnum[], assessorTypes: [] as AssessorTypeEnum[],
}); })
const [questionGroupForm, setQuestionGroupForm] = useState({ const [questionGroupForm, setQuestionGroupForm] = useState({
groupName: "", groupName: '',
description: "", description: '',
weight: 0, weight: 0,
order: 0, order: 0,
}); })
const [questionForm, setQuestionForm] = useState({ const [questionForm, setQuestionForm] = useState({
questionText: "", questionText: '',
questionType: QuestionTypeEnum.Rating, questionType: QuestionTypeEnum.Rating,
isRequired: true, isRequired: true,
order: 0, order: 0,
weight: 0, weight: 0,
options: [] as HrQuestionOption[], options: [] as HrQuestionOption[],
}); })
// Filter templates // Filter templates
const filteredTemplates = mockEvaluation360Templates.filter((template) => { const filteredTemplates = mockEvaluation360Templates.filter((template) => {
const searchLower = templateSearchTerm.toLowerCase(); const searchLower = templateSearchTerm.toLowerCase()
return ( return (
template.name.toLowerCase().includes(searchLower) || template.name.toLowerCase().includes(searchLower) ||
template.description.toLowerCase().includes(searchLower) template.description.toLowerCase().includes(searchLower)
); )
}); })
// Statistics // Statistics
const totalTemplates = mockEvaluation360Templates.length; const totalTemplates = mockEvaluation360Templates.length
const activeTemplates = mockEvaluation360Templates.filter( const activeTemplates = mockEvaluation360Templates.filter((t) => t.isActive).length
(t) => t.isActive
).length;
// Event handlers // Event handlers
const handleNewTemplate = () => { const handleNewTemplate = () => {
setTemplateForm({ setTemplateForm({
name: "", name: '',
description: "", description: '',
isActive: true, isActive: true,
assessorTypes: [], assessorTypes: [],
}); })
setSelectedTemplate(null); setSelectedTemplate(null)
setIsEditMode(false); setIsEditMode(false)
setShowTemplateModal(true); setShowTemplateModal(true)
}; }
const handleEditTemplate = (template: HrEvaluation360Template) => { const handleEditTemplate = (template: HrEvaluation360Template) => {
setTemplateForm({ setTemplateForm({
@ -93,11 +81,11 @@ const Degree360Templates: React.FC = () => {
description: template.description, description: template.description,
isActive: template.isActive, isActive: template.isActive,
assessorTypes: template.assessorTypes || [], assessorTypes: template.assessorTypes || [],
}); })
setSelectedTemplate(template); setSelectedTemplate(template)
setIsEditMode(true); setIsEditMode(true)
setShowTemplateModal(true); setShowTemplateModal(true)
}; }
const handleViewTemplate = (template: HrEvaluation360Template) => { const handleViewTemplate = (template: HrEvaluation360Template) => {
setTemplateForm({ setTemplateForm({
@ -105,72 +93,65 @@ const Degree360Templates: React.FC = () => {
description: template.description, description: template.description,
isActive: template.isActive, isActive: template.isActive,
assessorTypes: template.assessorTypes || [], assessorTypes: template.assessorTypes || [],
}); })
setSelectedTemplate(template); setSelectedTemplate(template)
setShowTemplateModal(true); setShowTemplateModal(true)
setIsEditMode(false); setIsEditMode(false)
}; }
const handleSaveTemplate = () => { const handleSaveTemplate = () => {
console.log("Saving template:", templateForm); console.log('Saving template:', templateForm)
setShowTemplateModal(false); setShowTemplateModal(false)
}; }
const handleDeleteTemplate = (template: HrEvaluation360Template) => { const handleDeleteTemplate = (template: HrEvaluation360Template) => {
if ( if (confirm(`"${template.name}" şablonunu silmek istediğinizden emin misiniz?`)) {
confirm( console.log('Deleting template:', template.id)
`"${template.name}" şablonunu silmek istediğinizden emin misiniz?` }
)
) {
console.log("Deleting template:", template.id);
} }
};
const handleAddQuestionGroup = () => { const handleAddQuestionGroup = () => {
setQuestionGroupForm({ setQuestionGroupForm({
groupName: "", groupName: '',
description: "", description: '',
weight: 0, weight: 0,
order: 0, order: 0,
}); })
setSelectedQuestionGroup(null); setSelectedQuestionGroup(null)
setShowQuestionModal(true); setShowQuestionModal(true)
}; }
const handleEditQuestionGroup = (group: HrQuestionGroup) => { const handleEditQuestionGroup = (group: HrQuestionGroup) => {
setQuestionGroupForm({ setQuestionGroupForm({
groupName: group.groupName, groupName: group.groupName,
description: group.description || "", description: group.description || '',
weight: group.weight, weight: group.weight,
order: group.order, order: group.order,
}); })
setSelectedQuestionGroup(group); setSelectedQuestionGroup(group)
setShowQuestionModal(true); setShowQuestionModal(true)
}; }
const handleSaveQuestionGroup = () => { const handleSaveQuestionGroup = () => {
console.log("Saving question group:", questionGroupForm); console.log('Saving question group:', questionGroupForm)
setShowQuestionModal(false); setShowQuestionModal(false)
}; }
const handleAddQuestion = (group: HrQuestionGroup) => { const handleAddQuestion = (group: HrQuestionGroup) => {
setQuestionForm({ setQuestionForm({
questionText: "", questionText: '',
questionType: QuestionTypeEnum.Rating, questionType: QuestionTypeEnum.Rating,
isRequired: true, isRequired: true,
order: 0, order: 0,
weight: 0, weight: 0,
options: [], options: [],
}); })
setSelectedQuestion(null); setSelectedQuestion(null)
setSelectedQuestionGroup(group); setSelectedQuestionGroup(group)
setShowQuestionItemModal(true); setShowQuestionItemModal(true)
}; }
const handleEditQuestion = ( const handleEditQuestion = (question: HrEvaluationQuestion, group: HrQuestionGroup) => {
question: HrEvaluationQuestion,
group: HrQuestionGroup
) => {
setQuestionForm({ setQuestionForm({
questionText: question.questionText, questionText: question.questionText,
questionType: question.questionType, questionType: question.questionType,
@ -178,22 +159,22 @@ const Degree360Templates: React.FC = () => {
order: question.order, order: question.order,
weight: question.weight, weight: question.weight,
options: question.options || [], options: question.options || [],
}); })
setSelectedQuestion(question); setSelectedQuestion(question)
setSelectedQuestionGroup(group); setSelectedQuestionGroup(group)
setShowQuestionItemModal(true); setShowQuestionItemModal(true)
}; }
const handleSaveQuestion = () => { const handleSaveQuestion = () => {
console.log("Saving question:", questionForm); console.log('Saving question:', questionForm)
setShowQuestionItemModal(false); setShowQuestionItemModal(false)
}; }
// Table columns // Table columns
const templateColumns: Column<HrEvaluation360Template>[] = [ const templateColumns: Column<HrEvaluation360Template>[] = [
{ {
key: "name", key: 'name',
header: "Şablon Adı", header: 'Şablon Adı',
sortable: true, sortable: true,
render: (template) => ( render: (template) => (
<div> <div>
@ -203,30 +184,28 @@ const Degree360Templates: React.FC = () => {
), ),
}, },
{ {
key: "questionGroups", key: 'questionGroups',
header: "Soru Grupları", header: 'Soru Grupları',
render: (template) => ( render: (template) => (
<span className="text-sm text-gray-600"> <span className="text-sm text-gray-600">{template.questionGroups?.length || 0} grup</span>
{template.questionGroups?.length || 0} grup
</span>
), ),
}, },
{ {
key: "assessorTypes", key: 'assessorTypes',
header: "Değerlendirecekler", header: 'Değerlendirecekler',
render: (template) => { render: (template) => {
const assessorLabels = { const assessorLabels = {
[AssessorTypeEnum.Self]: "Kendi", [AssessorTypeEnum.Self]: 'Kendi',
[AssessorTypeEnum.Manager]: "Yönetici", [AssessorTypeEnum.Manager]: 'Yönetici',
[AssessorTypeEnum.Peer]: "Meslektaş", [AssessorTypeEnum.Peer]: 'Meslektaş',
[AssessorTypeEnum.Subordinate]: "Ast", [AssessorTypeEnum.Subordinate]: 'Ast',
[AssessorTypeEnum.Customer]: "Müşteri", [AssessorTypeEnum.Customer]: 'Müşteri',
[AssessorTypeEnum.OtherDepartment]: "Diğer Departman", [AssessorTypeEnum.OtherDepartment]: 'Diğer Departman',
[AssessorTypeEnum.HRUpperManagement]: "İK/Üst Yönetim", [AssessorTypeEnum.HRUpperManagement]: 'İK/Üst Yönetim',
[AssessorTypeEnum.External]: "Dış Paydaş", [AssessorTypeEnum.External]: 'Dış Paydaş',
}; }
const assessorTypes = template.assessorTypes || []; const assessorTypes = template.assessorTypes || []
return ( return (
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{assessorTypes.map((type) => ( {assessorTypes.map((type) => (
@ -241,27 +220,25 @@ const Degree360Templates: React.FC = () => {
<span className="text-sm text-gray-400">Belirtilmemiş</span> <span className="text-sm text-gray-400">Belirtilmemiş</span>
)} )}
</div> </div>
); )
}, },
}, },
{ {
key: "isActive", key: 'isActive',
header: "Durum", header: 'Durum',
render: (template) => ( render: (template) => (
<span <span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
template.isActive template.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`} }`}
> >
{template.isActive ? "Aktif" : "Pasif"} {template.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
), ),
}, },
{ {
key: "actions", key: 'actions',
header: "İşlemler", header: 'İşlemler',
render: (template) => ( render: (template) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
@ -288,17 +265,16 @@ const Degree360Templates: React.FC = () => {
</div> </div>
), ),
}, },
]; ]
return ( return (
<div className="space-y-4 mt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div> <div>
<h2 className="text-xl font-bold text-gray-900">360° Şablonlar</h2> <h2 className="text-xl font-bold text-gray-900">360° Şablonlar</h2>
<p className="text-sm text-gray-600 mt-1"> <p className="text-gray-600 mt-1">360 derece değerlendirme şablonlarını yönetin</p>
360 derece değerlendirme şablonlarını yönetin
</p>
</div> </div>
<button <button
@ -348,6 +324,7 @@ const Degree360Templates: React.FC = () => {
{/* Templates Table */} {/* Templates Table */}
<DataTable data={filteredTemplates} columns={templateColumns} /> <DataTable data={filteredTemplates} columns={templateColumns} />
</div>
{/* Template Modal */} {/* Template Modal */}
{showTemplateModal && ( {showTemplateModal && (
@ -355,11 +332,7 @@ const Degree360Templates: React.FC = () => {
<div className="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto"> <div className="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-bold"> <h2 className="text-lg font-bold">
{isEditMode {isEditMode ? 'Şablon Düzenle' : selectedTemplate ? 'Şablon Detayı' : 'Yeni Şablon'}
? "Şablon Düzenle"
: selectedTemplate
? "Şablon Detayı"
: "Yeni Şablon"}
</h2> </h2>
<button <button
onClick={() => setShowTemplateModal(false)} onClick={() => setShowTemplateModal(false)}
@ -373,29 +346,23 @@ const Degree360Templates: React.FC = () => {
{/* Basic Information */} {/* Basic Information */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Şablon Adı</label>
Şablon Adı
</label>
<input <input
type="text" type="text"
value={templateForm.name} value={templateForm.name}
onChange={(e) => onChange={(e) => setTemplateForm({ ...templateForm, name: e.target.value })}
setTemplateForm({ ...templateForm, name: e.target.value })
}
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500" className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
disabled={!isEditMode && !!selectedTemplate} disabled={!isEditMode && !!selectedTemplate}
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Durum</label>
Durum
</label>
<select <select
value={templateForm.isActive ? "active" : "inactive"} value={templateForm.isActive ? 'active' : 'inactive'}
onChange={(e) => onChange={(e) =>
setTemplateForm({ setTemplateForm({
...templateForm, ...templateForm,
isActive: e.target.value === "active", isActive: e.target.value === 'active',
}) })
} }
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500" className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
@ -408,9 +375,7 @@ const Degree360Templates: React.FC = () => {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">ıklama</label>
ıklama
</label>
<textarea <textarea
value={templateForm.description} value={templateForm.description}
onChange={(e) => onChange={(e) =>
@ -433,15 +398,15 @@ const Degree360Templates: React.FC = () => {
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
{Object.values(AssessorTypeEnum).map((type) => { {Object.values(AssessorTypeEnum).map((type) => {
const labels = { const labels = {
[AssessorTypeEnum.Self]: "Kendi", [AssessorTypeEnum.Self]: 'Kendi',
[AssessorTypeEnum.Manager]: "Yönetici", [AssessorTypeEnum.Manager]: 'Yönetici',
[AssessorTypeEnum.Peer]: "Meslektaş", [AssessorTypeEnum.Peer]: 'Meslektaş',
[AssessorTypeEnum.Subordinate]: "Ast", [AssessorTypeEnum.Subordinate]: 'Ast',
[AssessorTypeEnum.Customer]: "Müşteri", [AssessorTypeEnum.Customer]: 'Müşteri',
[AssessorTypeEnum.OtherDepartment]: "Diğer Departman", [AssessorTypeEnum.OtherDepartment]: 'Diğer Departman',
[AssessorTypeEnum.HRUpperManagement]: "İK/Üst Yönetim", [AssessorTypeEnum.HRUpperManagement]: 'İK/Üst Yönetim',
[AssessorTypeEnum.External]: "Dış Paydaş", [AssessorTypeEnum.External]: 'Dış Paydaş',
}; }
return ( return (
<label key={type} className="flex items-center space-x-2"> <label key={type} className="flex items-center space-x-2">
@ -452,29 +417,21 @@ const Degree360Templates: React.FC = () => {
if (e.target.checked) { if (e.target.checked) {
setTemplateForm({ setTemplateForm({
...templateForm, ...templateForm,
assessorTypes: [ assessorTypes: [...templateForm.assessorTypes, type],
...templateForm.assessorTypes, })
type,
],
});
} else { } else {
setTemplateForm({ setTemplateForm({
...templateForm, ...templateForm,
assessorTypes: assessorTypes: templateForm.assessorTypes.filter((t) => t !== type),
templateForm.assessorTypes.filter( })
(t) => t !== type
),
});
} }
}} }}
className="rounded border-gray-300 text-purple-600 focus:ring-purple-500" className="rounded border-gray-300 text-purple-600 focus:ring-purple-500"
disabled={!isEditMode && !!selectedTemplate} disabled={!isEditMode && !!selectedTemplate}
/> />
<span className="text-sm text-gray-700"> <span className="text-sm text-gray-700">{labels[type] || type}</span>
{labels[type] || type}
</span>
</label> </label>
); )
})} })}
</div> </div>
</div> </div>
@ -496,21 +453,13 @@ const Degree360Templates: React.FC = () => {
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{selectedTemplate.questionGroups?.map( {selectedTemplate.questionGroups?.map((group, groupIndex) => (
(group, groupIndex) => ( <div key={groupIndex} className="bg-gray-50 p-3 rounded-lg">
<div
key={groupIndex}
className="bg-gray-50 p-3 rounded-lg"
>
<div className="flex justify-between items-start mb-3"> <div className="flex justify-between items-start mb-3">
<div> <div>
<h4 className="font-medium">{group.groupName}</h4> <h4 className="font-medium">{group.groupName}</h4>
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">{group.description}</p>
{group.description} <p className="text-sm text-gray-600">ırlık: {group.weight}%</p>
</p>
<p className="text-sm text-gray-600">
ırlık: {group.weight}%
</p>
</div> </div>
{isEditMode && ( {isEditMode && (
<div className="flex gap-2"> <div className="flex gap-2">
@ -533,44 +482,32 @@ const Degree360Templates: React.FC = () => {
{/* Questions */} {/* Questions */}
<div className="space-y-2"> <div className="space-y-2">
{group.questions?.map((question, questionIndex) => ( {group.questions?.map((question, questionIndex) => (
<div <div key={questionIndex} className="bg-white p-2 rounded border">
key={questionIndex}
className="bg-white p-2 rounded border"
>
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div className="flex-1"> <div className="flex-1">
<p className="text-sm"> <p className="text-sm">{question.questionText}</p>
{question.questionText}
</p>
<div className="flex gap-4 mt-1"> <div className="flex gap-4 mt-1">
<span className="text-xs text-gray-500"> <span className="text-xs text-gray-500">
Tip:{" "} Tip:{' '}
{question.questionType === {question.questionType === QuestionTypeEnum.Rating
QuestionTypeEnum.Rating ? 'Puanlama'
? "Puanlama" : question.questionType === QuestionTypeEnum.MultipleChoice
: question.questionType === ? 'Çoktan Seçmeli'
QuestionTypeEnum.MultipleChoice : question.questionType === QuestionTypeEnum.Text
? "Çoktan Seçmeli" ? 'Metin'
: question.questionType === : 'Evet/Hayır'}
QuestionTypeEnum.Text
? "Metin"
: "Evet/Hayır"}
</span> </span>
<span className="text-xs text-gray-500"> <span className="text-xs text-gray-500">
ırlık: {question.weight}% ırlık: {question.weight}%
</span> </span>
{question.isRequired && ( {question.isRequired && (
<span className="text-xs text-red-500"> <span className="text-xs text-red-500">Zorunlu</span>
Zorunlu
</span>
)} )}
</div> </div>
</div> </div>
{isEditMode && ( {isEditMode && (
<button <button
onClick={() => onClick={() => handleEditQuestion(question, group)}
handleEditQuestion(question, group)
}
className="text-blue-600 hover:text-blue-900" className="text-blue-600 hover:text-blue-900"
> >
<FaEdit className="w-4 h-4" /> <FaEdit className="w-4 h-4" />
@ -581,8 +518,7 @@ const Degree360Templates: React.FC = () => {
))} ))}
</div> </div>
</div> </div>
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -593,7 +529,7 @@ const Degree360Templates: React.FC = () => {
onClick={() => setShowTemplateModal(false)} onClick={() => setShowTemplateModal(false)}
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50" className="px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
> >
{selectedTemplate && !isEditMode ? "Kapat" : "İptal"} {selectedTemplate && !isEditMode ? 'Kapat' : 'İptal'}
</button> </button>
{(isEditMode || !selectedTemplate) && ( {(isEditMode || !selectedTemplate) && (
<button <button
@ -615,9 +551,7 @@ const Degree360Templates: React.FC = () => {
<div className="bg-white rounded-lg p-6 w-full max-w-md"> <div className="bg-white rounded-lg p-6 w-full max-w-md">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-bold"> <h2 className="text-lg font-bold">
{selectedQuestionGroup {selectedQuestionGroup ? 'Soru Grubu Düzenle' : 'Yeni Soru Grubu'}
? "Soru Grubu Düzenle"
: "Yeni Soru Grubu"}
</h2> </h2>
<button <button
onClick={() => setShowQuestionModal(false)} onClick={() => setShowQuestionModal(false)}
@ -629,9 +563,7 @@ const Degree360Templates: React.FC = () => {
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Grup Adı</label>
Grup Adı
</label>
<input <input
type="text" type="text"
value={questionGroupForm.groupName} value={questionGroupForm.groupName}
@ -646,9 +578,7 @@ const Degree360Templates: React.FC = () => {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">ıklama</label>
ıklama
</label>
<textarea <textarea
value={questionGroupForm.description} value={questionGroupForm.description}
onChange={(e) => onChange={(e) =>
@ -682,9 +612,7 @@ const Degree360Templates: React.FC = () => {
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Sıra</label>
Sıra
</label>
<input <input
type="number" type="number"
min="0" min="0"
@ -726,7 +654,7 @@ const Degree360Templates: React.FC = () => {
<div className="bg-white rounded-lg p-6 w-full max-w-md"> <div className="bg-white rounded-lg p-6 w-full max-w-md">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-bold"> <h2 className="text-lg font-bold">
{selectedQuestion ? "Soru Düzenle" : "Yeni Soru"} {selectedQuestion ? 'Soru Düzenle' : 'Yeni Soru'}
</h2> </h2>
<button <button
onClick={() => setShowQuestionItemModal(false)} onClick={() => setShowQuestionItemModal(false)}
@ -738,9 +666,7 @@ const Degree360Templates: React.FC = () => {
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Soru Metni</label>
Soru Metni
</label>
<textarea <textarea
value={questionForm.questionText} value={questionForm.questionText}
onChange={(e) => onChange={(e) =>
@ -755,9 +681,7 @@ const Degree360Templates: React.FC = () => {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Soru Tipi</label>
Soru Tipi
</label>
<select <select
value={questionForm.questionType} value={questionForm.questionType}
onChange={(e) => onChange={(e) =>
@ -769,9 +693,7 @@ const Degree360Templates: React.FC = () => {
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500" className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
> >
<option value={QuestionTypeEnum.Rating}>Puanlama</option> <option value={QuestionTypeEnum.Rating}>Puanlama</option>
<option value={QuestionTypeEnum.MultipleChoice}> <option value={QuestionTypeEnum.MultipleChoice}>Çoktan Seçmeli</option>
Çoktan Seçmeli
</option>
<option value={QuestionTypeEnum.Text}>Metin</option> <option value={QuestionTypeEnum.Text}>Metin</option>
<option value={QuestionTypeEnum.YesNo}>Evet/Hayır</option> <option value={QuestionTypeEnum.YesNo}>Evet/Hayır</option>
</select> </select>
@ -797,9 +719,7 @@ const Degree360Templates: React.FC = () => {
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Sıra</label>
Sıra
</label>
<input <input
type="number" type="number"
min="0" min="0"
@ -828,10 +748,7 @@ const Degree360Templates: React.FC = () => {
} }
className="h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded" className="h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded"
/> />
<label <label htmlFor="isRequired" className="ml-2 block text-sm text-gray-700">
htmlFor="isRequired"
className="ml-2 block text-sm text-gray-700"
>
Zorunlu soru Zorunlu soru
</label> </label>
</div> </div>
@ -855,8 +772,8 @@ const Degree360Templates: React.FC = () => {
</div> </div>
</div> </div>
)} )}
</div> </Container>
); )
}; }
export default Degree360Templates; export default Degree360Templates

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import {
FaPlus, FaPlus,
FaEdit, FaEdit,
@ -9,53 +9,51 @@ import {
FaEye, FaEye,
FaList, FaList,
FaTh, FaTh,
} from "react-icons/fa"; } from 'react-icons/fa'
import { HrDepartment } from "../../../types/hr"; import { HrDepartment } from '../../../types/hr'
import DataTable, { Column } from "../../../components/common/DataTable"; import DataTable, { Column } from '../../../components/common/DataTable'
import { mockDepartments } from "../../../mocks/mockDepartments"; import { mockDepartments } from '../../../mocks/mockDepartments'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockCostCenters } from "../../../mocks/mockCostCenters"; import { mockCostCenters } from '../../../mocks/mockCostCenters'
import DepartmentFormModal from "./DepartmentFormModal"; import DepartmentFormModal from './DepartmentFormModal'
import DepartmentViewModal from "./DepartmentViewModal"; import DepartmentViewModal from './DepartmentViewModal'
import Widget from "../../../components/common/Widget"; import Widget from '../../../components/common/Widget'
import { Container } from '@/components/shared'
const DepartmentManagement: React.FC = () => { const DepartmentManagement: React.FC = () => {
const [departments, setDepartments] = const [departments, setDepartments] = useState<HrDepartment[]>(mockDepartments)
useState<HrDepartment[]>(mockDepartments); const [searchTerm, setSearchTerm] = useState('')
const [searchTerm, setSearchTerm] = useState(""); const [selectedParent, setSelectedParent] = useState<string>('all')
const [selectedParent, setSelectedParent] = useState<string>("all"); const [viewMode, setViewMode] = useState<'list' | 'cards'>('list')
const [viewMode, setViewMode] = useState<"list" | "cards">("list");
// Modal states // Modal states
const [isFormModalOpen, setIsFormModalOpen] = useState(false); const [isFormModalOpen, setIsFormModalOpen] = useState(false)
const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false)
const [selectedDepartment, setSelectedDepartment] = useState< const [selectedDepartment, setSelectedDepartment] = useState<HrDepartment | undefined>()
HrDepartment | undefined const [modalTitle, setModalTitle] = useState('')
>();
const [modalTitle, setModalTitle] = useState("");
const handleAdd = () => { const handleAdd = () => {
setSelectedDepartment(undefined); setSelectedDepartment(undefined)
setModalTitle("Yeni Departman"); setModalTitle('Yeni Departman')
setIsFormModalOpen(true); setIsFormModalOpen(true)
}; }
const handleEdit = (department: HrDepartment) => { const handleEdit = (department: HrDepartment) => {
setSelectedDepartment(department); setSelectedDepartment(department)
setModalTitle("Departman Düzenle"); setModalTitle('Departman Düzenle')
setIsFormModalOpen(true); setIsFormModalOpen(true)
}; }
const handleView = (department: HrDepartment) => { const handleView = (department: HrDepartment) => {
setSelectedDepartment(department); setSelectedDepartment(department)
setIsViewModalOpen(true); setIsViewModalOpen(true)
}; }
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
if (confirm("Bu departmanı silmek istediğinizden emin misiniz?")) { if (confirm('Bu departmanı silmek istediğinizden emin misiniz?')) {
setDepartments((prev) => prev.filter((dept) => dept.id !== id)); setDepartments((prev) => prev.filter((dept) => dept.id !== id))
}
} }
};
const handleSave = (departmentData: Partial<HrDepartment>) => { const handleSave = (departmentData: Partial<HrDepartment>) => {
if (selectedDepartment) { if (selectedDepartment) {
@ -64,9 +62,9 @@ const DepartmentManagement: React.FC = () => {
prev.map((dept) => prev.map((dept) =>
dept.id === selectedDepartment.id dept.id === selectedDepartment.id
? { ...dept, ...departmentData, lastModificationTime: new Date() } ? { ...dept, ...departmentData, lastModificationTime: new Date() }
: dept : dept,
),
) )
);
} else { } else {
// Add new department // Add new department
const newDepartment: HrDepartment = { const newDepartment: HrDepartment = {
@ -75,37 +73,35 @@ const DepartmentManagement: React.FC = () => {
subDepartments: [], subDepartments: [],
creationTime: new Date(), creationTime: new Date(),
lastModificationTime: new Date(), lastModificationTime: new Date(),
} as HrDepartment; } as HrDepartment
setDepartments((prev) => [...prev, newDepartment]); setDepartments((prev) => [...prev, newDepartment])
}
setIsFormModalOpen(false)
} }
setIsFormModalOpen(false);
};
const handleCloseFormModal = () => { const handleCloseFormModal = () => {
setIsFormModalOpen(false); setIsFormModalOpen(false)
setSelectedDepartment(undefined); setSelectedDepartment(undefined)
}; }
const handleCloseViewModal = () => { const handleCloseViewModal = () => {
setIsViewModalOpen(false); setIsViewModalOpen(false)
setSelectedDepartment(undefined); setSelectedDepartment(undefined)
}; }
const handleEditFromView = (department: HrDepartment) => { const handleEditFromView = (department: HrDepartment) => {
setIsViewModalOpen(false); setIsViewModalOpen(false)
handleEdit(department); handleEdit(department)
}; }
mockDepartments.forEach((dept) => { mockDepartments.forEach((dept) => {
if (dept.managerId) { if (dept.managerId) {
dept.manager = mockEmployees.find((emp) => emp.id === dept.managerId); dept.manager = mockEmployees.find((emp) => emp.id === dept.managerId)
} }
if (dept.costCenterId) { if (dept.costCenterId) {
dept.costCenter = mockCostCenters.find( dept.costCenter = mockCostCenters.find((cc) => cc.id === dept.costCenterId)
(cc) => cc.id === dept.costCenterId
);
} }
}); })
const filteredDepartments = departments.filter((department) => { const filteredDepartments = departments.filter((department) => {
if ( if (
@ -113,96 +109,81 @@ const DepartmentManagement: React.FC = () => {
!department.name.toLowerCase().includes(searchTerm.toLowerCase()) && !department.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
!department.code.toLowerCase().includes(searchTerm.toLowerCase()) !department.code.toLowerCase().includes(searchTerm.toLowerCase())
) { ) {
return false; return false
} }
if ( if (selectedParent !== 'all' && department.parentDepartmentId !== selectedParent) {
selectedParent !== "all" && return false
department.parentDepartmentId !== selectedParent
) {
return false;
} }
return true; return true
}); })
const columns: Column<HrDepartment>[] = [ const columns: Column<HrDepartment>[] = [
{ {
key: "code", key: 'code',
header: "Departman Kodu", header: 'Departman Kodu',
sortable: true, sortable: true,
}, },
{ {
key: "name", key: 'name',
header: "Departman Adı", header: 'Departman Adı',
sortable: true, sortable: true,
}, },
{ {
key: "parentDepartment", key: 'parentDepartment',
header: "Üst Departman", header: 'Üst Departman',
render: (department: HrDepartment) => render: (department: HrDepartment) => department.parentDepartment?.name || '-',
department.parentDepartment?.name || "-",
}, },
{ {
key: "manager", key: 'manager',
header: "Yönetici", header: 'Yönetici',
render: (department: HrDepartment) => department.manager?.fullName || "-", render: (department: HrDepartment) => department.manager?.fullName || '-',
}, },
{ {
key: "employeeCount", key: 'employeeCount',
header: "Personel Sayısı", header: 'Personel Sayısı',
render: (department: HrDepartment) => ( render: (department: HrDepartment) => (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaUsers className="w-4 h-4 text-gray-500" /> <FaUsers className="w-4 h-4 text-gray-500" />
<span> <span>{mockEmployees.filter((a) => a.departmantId == department.id).length || 0}</span>
{mockEmployees.filter((a) => a.departmantId == department.id)
.length || 0}
</span>
</div> </div>
), ),
}, },
{ {
key: "costCenter", key: 'costCenter',
header: "Maliyet Merkezi", header: 'Maliyet Merkezi',
render: (department: HrDepartment) => ( render: (department: HrDepartment) => (
<div className="flex flex-col"> <div className="flex flex-col">
<span className="font-medium"> <span className="font-medium">{department.costCenter?.code || '-'}</span>
{department.costCenter?.code || "-"} <span className="text-xs text-gray-500">{department.costCenter?.name || '-'}</span>
</span>
<span className="text-xs text-gray-500">
{department.costCenter?.name || "-"}
</span>
</div> </div>
), ),
}, },
{ {
key: "budget", key: 'budget',
header: "Bütçe", header: 'Bütçe',
render: (department: HrDepartment) => ( render: (department: HrDepartment) => (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaDollarSign className="w-4 h-4 text-gray-500" /> <FaDollarSign className="w-4 h-4 text-gray-500" />
<span> <span>{department.budget ? `${department.budget.toLocaleString()}` : '-'}</span>
{department.budget ? `${department.budget.toLocaleString()}` : "-"}
</span>
</div> </div>
), ),
}, },
{ {
key: "status", key: 'status',
header: "Durum", header: 'Durum',
render: (department: HrDepartment) => ( render: (department: HrDepartment) => (
<span <span
className={`px-2 py-1 text-xs font-medium rounded-full ${ className={`px-2 py-1 text-xs font-medium rounded-full ${
department.isActive department.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`} }`}
> >
{department.isActive ? "Aktif" : "Pasif"} {department.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
), ),
}, },
{ {
key: "actions", key: 'actions',
header: "İşlemler", header: 'İşlemler',
render: (department: HrDepartment) => ( render: (department: HrDepartment) => (
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
@ -229,16 +210,15 @@ const DepartmentManagement: React.FC = () => {
</div> </div>
), ),
}, },
]; ]
return ( return (
<div className="space-y-3 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div> <div>
<h2 className="text-lg font-bold text-gray-900"> <h2 className="text-lg font-bold text-gray-900">Departman Yönetimi</h2>
Departman Yönetimi
</h2>
<p className="text-sm text-gray-600 mt-1"> <p className="text-sm text-gray-600 mt-1">
Organizasyon yapısını ve departmanları yönetin Organizasyon yapısını ve departmanları yönetin
</p> </p>
@ -247,22 +227,22 @@ const DepartmentManagement: React.FC = () => {
{/* View Mode Toggle */} {/* View Mode Toggle */}
<div className="flex bg-gray-100 rounded-lg p-0.5"> <div className="flex bg-gray-100 rounded-lg p-0.5">
<button <button
onClick={() => setViewMode("list")} onClick={() => setViewMode('list')}
className={`p-1.5 rounded-md transition-colors ${ className={`p-1.5 rounded-md transition-colors ${
viewMode === "list" viewMode === 'list'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
title="Liste Görünümü" title="Liste Görünümü"
> >
<FaList className="w-4 h-4" /> <FaList className="w-4 h-4" />
</button> </button>
<button <button
onClick={() => setViewMode("cards")} onClick={() => setViewMode('cards')}
className={`p-1.5 rounded-md transition-colors ${ className={`p-1.5 rounded-md transition-colors ${
viewMode === "cards" viewMode === 'cards'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
title="Kart Görünümü" title="Kart Görünümü"
> >
@ -341,7 +321,7 @@ const DepartmentManagement: React.FC = () => {
</div> </div>
{/* Data Display */} {/* Data Display */}
{viewMode === "list" ? ( {viewMode === 'list' ? (
<div className="bg-white rounded-lg shadow-sm border overflow-x-auto"> <div className="bg-white rounded-lg shadow-sm border overflow-x-auto">
<DataTable data={filteredDepartments} columns={columns} /> <DataTable data={filteredDepartments} columns={columns} />
</div> </div>
@ -358,8 +338,8 @@ const DepartmentManagement: React.FC = () => {
<div <div
className={`w-12 h-12 rounded-lg flex items-center justify-center ${ className={`w-12 h-12 rounded-lg flex items-center justify-center ${
department.isActive department.isActive
? "bg-blue-100 text-blue-600" ? 'bg-blue-100 text-blue-600'
: "bg-gray-100 text-gray-500" : 'bg-gray-100 text-gray-500'
}`} }`}
> >
<FaBuilding className="w-6 h-6" /> <FaBuilding className="w-6 h-6" />
@ -374,11 +354,11 @@ const DepartmentManagement: React.FC = () => {
<span <span
className={`px-2 py-1 text-xs font-medium rounded-full ${ className={`px-2 py-1 text-xs font-medium rounded-full ${
department.isActive department.isActive
? "bg-green-100 text-green-800" ? 'bg-green-100 text-green-800'
: "bg-red-100 text-red-800" : 'bg-red-100 text-red-800'
}`} }`}
> >
{department.isActive ? "Aktif" : "Pasif"} {department.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
</div> </div>
@ -401,11 +381,7 @@ const DepartmentManagement: React.FC = () => {
<div className="flex items-center text-xs text-gray-600"> <div className="flex items-center text-xs text-gray-600">
<FaUsers className="w-4 h-4 mr-2 text-gray-400" /> <FaUsers className="w-4 h-4 mr-2 text-gray-400" />
<span> <span>
{ {mockEmployees.filter((emp) => emp.departmantId === department.id).length}{' '}
mockEmployees.filter(
(emp) => emp.departmantId === department.id
).length
}{" "}
Personel Personel
</span> </span>
</div> </div>
@ -419,12 +395,8 @@ const DepartmentManagement: React.FC = () => {
{department.costCenter && ( {department.costCenter && (
<div className="text-xs text-gray-600"> <div className="text-xs text-gray-600">
<div className="font-medium"> <div className="font-medium">{department.costCenter.code}</div>
{department.costCenter.code} <div className="text-xs text-gray-500">{department.costCenter.name}</div>
</div>
<div className="text-xs text-gray-500">
{department.costCenter.name}
</div>
</div> </div>
)} )}
</div> </div>
@ -461,14 +433,11 @@ const DepartmentManagement: React.FC = () => {
{filteredDepartments.length === 0 && ( {filteredDepartments.length === 0 && (
<div className="text-center py-12"> <div className="text-center py-12">
<FaBuilding className="w-12 h-12 text-gray-400 mx-auto mb-4" /> <FaBuilding className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2"> <h3 className="text-lg font-medium text-gray-900 mb-2">Departman bulunamadı</h3>
Departman bulunamadı <p className="text-gray-500">Arama kriterlerinizi değiştirmeyi deneyin.</p>
</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirmeyi deneyin.
</p>
</div> </div>
)} )}
</div>
{/* Modals */} {/* Modals */}
<DepartmentFormModal <DepartmentFormModal
@ -485,8 +454,8 @@ const DepartmentManagement: React.FC = () => {
department={selectedDepartment || null} department={selectedDepartment || null}
onEdit={handleEditFromView} onEdit={handleEditFromView}
/> />
</div> </Container>
); )
}; }
export default DepartmentManagement; export default DepartmentManagement

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import {
FaUser, FaUser,
FaEnvelope, FaEnvelope,
@ -9,57 +9,50 @@ import {
FaEye, FaEye,
FaTrash, FaTrash,
FaPlus, FaPlus,
} from "react-icons/fa"; } from 'react-icons/fa'
import { HrEmployee, EmployeeStatusEnum } from "../../../types/hr"; import { HrEmployee, EmployeeStatusEnum } from '../../../types/hr'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import { import { getEmployeeStatusColor, getEmployeeStatusText } from '../../../utils/erp'
getEmployeeStatusColor, import { Container } from '@/components/shared'
getEmployeeStatusText,
} from "../../../utils/erp";
const EmployeeCards: React.FC = () => { const EmployeeCards: React.FC = () => {
const [employees] = useState<HrEmployee[]>(mockEmployees); const [employees] = useState<HrEmployee[]>(mockEmployees)
const [selectedDepartment, setSelectedDepartment] = useState<string>("all"); const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
const [selectedStatus, setSelectedStatus] = useState<string>("all"); const [selectedStatus, setSelectedStatus] = useState<string>('all')
const handleEdit = (employee: HrEmployee) => { const handleEdit = (employee: HrEmployee) => {
console.log("Edit employee:", employee); console.log('Edit employee:', employee)
// Implement edit functionality // Implement edit functionality
}; }
const handleView = (employee: HrEmployee) => { const handleView = (employee: HrEmployee) => {
console.log("View employee:", employee); console.log('View employee:', employee)
// Implement view functionality // Implement view functionality
}; }
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
console.log("Delete employee:", id); console.log('Delete employee:', id)
// Implement delete functionality // Implement delete functionality
}; }
const handleAdd = () => { const handleAdd = () => {
console.log("Add new employee"); console.log('Add new employee')
// Implement add functionality // Implement add functionality
}; }
const filteredEmployees = employees.filter((employee) => { const filteredEmployees = employees.filter((employee) => {
if ( if (selectedDepartment !== 'all' && employee.department?.id !== selectedDepartment) {
selectedDepartment !== "all" && return false
employee.department?.id !== selectedDepartment
) {
return false;
} }
if ( if (selectedStatus !== 'all' && employee.employeeStatus !== selectedStatus) {
selectedStatus !== "all" && return false
employee.employeeStatus !== selectedStatus
) {
return false;
} }
return true; return true
}); })
return ( return (
<div className="space-y-4 pt-2"> <Container>
<div className="space-y-2">
{/* Header with Add Button */} {/* Header with Add Button */}
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h2 className="text-xl font-bold text-gray-900">Personel Kartları</h2> <h2 className="text-xl font-bold text-gray-900">Personel Kartları</h2>
@ -93,9 +86,7 @@ const EmployeeCards: React.FC = () => {
<option value={EmployeeStatusEnum.Inactive}>Pasif</option> <option value={EmployeeStatusEnum.Inactive}>Pasif</option>
<option value={EmployeeStatusEnum.OnLeave}>İzinli</option> <option value={EmployeeStatusEnum.OnLeave}>İzinli</option>
<option value={EmployeeStatusEnum.Suspended}>Askıda</option> <option value={EmployeeStatusEnum.Suspended}>Askıda</option>
<option value={EmployeeStatusEnum.Terminated}> <option value={EmployeeStatusEnum.Terminated}>İşten Çıkarılmış</option>
İşten Çıkarılmış
</option>
</select> </select>
</div> </div>
@ -113,7 +104,7 @@ const EmployeeCards: React.FC = () => {
</div> </div>
<span <span
className={`px-2 py-1 text-xs font-medium rounded-full ${getEmployeeStatusColor( className={`px-2 py-1 text-xs font-medium rounded-full ${getEmployeeStatusColor(
employee.employeeStatus employee.employeeStatus,
)}`} )}`}
> >
{getEmployeeStatusText(employee.employeeStatus)} {getEmployeeStatusText(employee.employeeStatus)}
@ -122,15 +113,9 @@ const EmployeeCards: React.FC = () => {
{/* Employee Info */} {/* Employee Info */}
<div className="space-y-1 mb-3"> <div className="space-y-1 mb-3">
<h3 className="font-semibold text-sm text-gray-900"> <h3 className="font-semibold text-sm text-gray-900">{employee.fullName}</h3>
{employee.fullName} <p className="text-xs text-gray-600">{employee.jobPosition?.name}</p>
</h3> <p className="text-sm text-gray-500">{employee.department?.name}</p>
<p className="text-xs text-gray-600">
{employee.jobPosition?.name}
</p>
<p className="text-sm text-gray-500">
{employee.department?.name}
</p>
</div> </div>
{/* Contact Info */} {/* Contact Info */}
@ -147,9 +132,7 @@ const EmployeeCards: React.FC = () => {
)} )}
<div className="flex items-center text-xs text-gray-600"> <div className="flex items-center text-xs text-gray-600">
<FaCalendar className="w-4 h-4 mr-2" /> <FaCalendar className="w-4 h-4 mr-2" />
<span> <span>{new Date(employee.hireDate).toLocaleDateString('tr-TR')}</span>
{new Date(employee.hireDate).toLocaleDateString("tr-TR")}
</span>
</div> </div>
<div className="flex items-center text-xs text-gray-600"> <div className="flex items-center text-xs text-gray-600">
<FaCertificate className="w-4 h-4 mr-2" /> <FaCertificate className="w-4 h-4 mr-2" />
@ -187,16 +170,13 @@ const EmployeeCards: React.FC = () => {
{filteredEmployees.length === 0 && ( {filteredEmployees.length === 0 && (
<div className="text-center py-12"> <div className="text-center py-12">
<FaUser className="w-10 h-10 text-gray-400 mx-auto mb-3" /> <FaUser className="w-10 h-10 text-gray-400 mx-auto mb-3" />
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">Personel bulunamadı</h3>
Personel bulunamadı <p className="text-gray-500">Seçilen kriterlere uygun personel bulunmamaktadır.</p>
</h3>
<p className="text-gray-500">
Seçilen kriterlere uygun personel bulunmamaktadır.
</p>
</div> </div>
)} )}
</div> </div>
); </Container>
}; )
}
export default EmployeeCards; export default EmployeeCards

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from 'react'
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from 'react-router-dom'
import { import {
FaSave, FaSave,
FaTimes, FaTimes,
@ -9,10 +9,10 @@ import {
FaMapMarkerAlt, FaMapMarkerAlt,
FaPhone, FaPhone,
FaEnvelope, FaEnvelope,
} from "react-icons/fa"; } from 'react-icons/fa'
import LoadingSpinner from "../../../components/common/LoadingSpinner"; import LoadingSpinner from '../../../components/common/LoadingSpinner'
import { mockDepartments } from "../../../mocks/mockDepartments"; import { mockDepartments } from '../../../mocks/mockDepartments'
import { mockJobPositions } from "../../../mocks/mockJobPositions"; import { mockJobPositions } from '../../../mocks/mockJobPositions'
import { import {
HrDepartment, HrDepartment,
HrEmployee, HrEmployee,
@ -22,68 +22,69 @@ import {
JobLevelEnum, JobLevelEnum,
HrJobPosition, HrJobPosition,
MaritalStatusEnum, MaritalStatusEnum,
} from "../../../types/hr"; } from '../../../types/hr'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import { Container } from '@/components/shared'
interface ValidationErrors { interface ValidationErrors {
[key: string]: string; [key: string]: string
} }
const EmployeeForm: React.FC = () => { const EmployeeForm: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate()
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>()
const isEdit = Boolean(id); const isEdit = Boolean(id)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false)
const [errors, setErrors] = useState<ValidationErrors>({}); const [errors, setErrors] = useState<ValidationErrors>({})
const [departments, setDepartments] = useState<HrDepartment[]>([]); const [departments, setDepartments] = useState<HrDepartment[]>([])
const [jobPositions, setJobPositions] = useState<HrJobPosition[]>([]); const [jobPositions, setJobPositions] = useState<HrJobPosition[]>([])
const [managers, setManagers] = useState<HrEmployee[]>([]); const [managers, setManagers] = useState<HrEmployee[]>([])
const [formData, setFormData] = useState<HrEmployee>({ const [formData, setFormData] = useState<HrEmployee>({
id: "", id: '',
code: "", code: '',
firstName: "", firstName: '',
lastName: "", lastName: '',
fullName: "", fullName: '',
email: "", email: '',
phone: "", phone: '',
personalPhone: "", personalPhone: '',
nationalId: "", nationalId: '',
birthDate: new Date(), birthDate: new Date(),
gender: GenderEnum.Male, gender: GenderEnum.Male,
maritalStatus: MaritalStatusEnum.Single, maritalStatus: MaritalStatusEnum.Single,
address: { address: {
street: "", street: '',
city: "", city: '',
state: "", state: '',
postalCode: "", postalCode: '',
country: "", country: '',
}, },
emergencyContact: { emergencyContact: {
name: "", name: '',
relationship: "", relationship: '',
phone: "", phone: '',
email: "", email: '',
address: { address: {
street: "", street: '',
city: "", city: '',
state: "", state: '',
postalCode: "", postalCode: '',
country: "", country: '',
}, },
}, },
hireDate: new Date(), hireDate: new Date(),
employmentType: EmploymentTypeEnum.FullTime, employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "", jobPositionId: '',
departmantId: "", departmantId: '',
baseSalary: 0, baseSalary: 0,
currency: "TRY", currency: 'TRY',
payrollGroup: "", payrollGroup: '',
bankAccountId: "", bankAccountId: '',
workLocation: "", workLocation: '',
badgeNumber: "", badgeNumber: '',
employeeStatus: EmployeeStatusEnum.Active, employeeStatus: EmployeeStatusEnum.Active,
isActive: true, isActive: true,
leaves: [], leaves: [],
@ -92,162 +93,156 @@ const EmployeeForm: React.FC = () => {
disciplinaryActions: [], disciplinaryActions: [],
creationTime: new Date(), creationTime: new Date(),
lastModificationTime: new Date(), lastModificationTime: new Date(),
}); })
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
try { try {
const mockManagers = mockEmployees.filter( const mockManagers = mockEmployees.filter(
(emp) => emp.jobPosition?.level === JobLevelEnum.Manager (emp) => emp.jobPosition?.level === JobLevelEnum.Manager,
); )
setDepartments(mockDepartments); setDepartments(mockDepartments)
setJobPositions(mockJobPositions); setJobPositions(mockJobPositions)
setManagers(mockManagers); setManagers(mockManagers)
} catch (error) { } catch (error) {
console.error("Error loading data:", error); console.error('Error loading data:', error)
} }
}, []); }, [])
const loadFormData = useCallback(async () => { const loadFormData = useCallback(async () => {
setLoading(true); setLoading(true)
try { try {
if (isEdit && id) { if (isEdit && id) {
// Simulate API call // Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000))
// Mock employee data // Mock employee data
const mockEmployee = mockEmployees.find((emp) => emp.id === id)!; const mockEmployee = mockEmployees.find((emp) => emp.id === id)!
setFormData(mockEmployee); setFormData(mockEmployee)
} }
} catch (error) { } catch (error) {
console.error("Error loading form data:", error); console.error('Error loading form data:', error)
} finally { } finally {
setLoading(false); setLoading(false)
} }
}, [isEdit, id]); }, [isEdit, id])
useEffect(() => { useEffect(() => {
loadData(); loadData()
loadFormData(); loadFormData()
}, [loadData, loadFormData]); }, [loadData, loadFormData])
const validateForm = (): boolean => { const validateForm = (): boolean => {
const newErrors: ValidationErrors = {}; const newErrors: ValidationErrors = {}
if (!formData.code.trim()) { if (!formData.code.trim()) {
newErrors.code = "Personel kodu zorunludur"; newErrors.code = 'Personel kodu zorunludur'
} }
if (!formData.firstName.trim()) { if (!formData.firstName.trim()) {
newErrors.firstName = "Ad zorunludur"; newErrors.firstName = 'Ad zorunludur'
} }
if (!formData.lastName.trim()) { if (!formData.lastName.trim()) {
newErrors.lastName = "Soyad zorunludur"; newErrors.lastName = 'Soyad zorunludur'
} }
if (!formData.email.trim()) { if (!formData.email.trim()) {
newErrors.email = "Email zorunludur"; newErrors.email = 'Email zorunludur'
} else if (!/\S+@\S+\.\S+/.test(formData.email)) { } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = "Geçerli bir email adresi girin"; newErrors.email = 'Geçerli bir email adresi girin'
} }
if (!formData.nationalId.trim()) { if (!formData.nationalId.trim()) {
newErrors.nationalId = "TC Kimlik No zorunludur"; newErrors.nationalId = 'TC Kimlik No zorunludur'
} else if (formData.nationalId.length !== 11) { } else if (formData.nationalId.length !== 11) {
newErrors.nationalId = "TC Kimlik No 11 haneli olmalıdır"; newErrors.nationalId = 'TC Kimlik No 11 haneli olmalıdır'
} }
if (!formData.birthDate) { if (!formData.birthDate) {
newErrors.birthDate = "Doğum tarihi zorunludur"; newErrors.birthDate = 'Doğum tarihi zorunludur'
} }
if (!formData.hireDate) { if (!formData.hireDate) {
newErrors.hireDate = "İşe giriş tarihi zorunludur"; newErrors.hireDate = 'İşe giriş tarihi zorunludur'
} }
if (!formData.departmantId) { if (!formData.departmantId) {
newErrors.departmentId = "Departman seçilmelidir"; newErrors.departmentId = 'Departman seçilmelidir'
} }
if (!formData.jobPosition) { if (!formData.jobPosition) {
newErrors.jobPositionId = "Pozisyon seçilmelidir"; newErrors.jobPositionId = 'Pozisyon seçilmelidir'
} }
if (!formData.employmentType) { if (!formData.employmentType) {
newErrors.employmentType = "Çalışma tipi seçilmelidir"; newErrors.employmentType = 'Çalışma tipi seçilmelidir'
} }
if (formData.baseSalary <= 0) { if (formData.baseSalary <= 0) {
newErrors.baseSalary = "Maaş 0'dan büyük olmalıdır"; newErrors.baseSalary = "Maaş 0'dan büyük olmalıdır"
} }
setErrors(newErrors); setErrors(newErrors)
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0
}; }
const handleInputChange = ( const handleInputChange = (field: keyof HrEmployee, value: string | number | boolean) => {
field: keyof HrEmployee,
value: string | number | boolean
) => {
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
[field]: value, [field]: value,
})); }))
// Clear error when user starts typing // Clear error when user starts typing
if (errors[field]) { if (errors[field]) {
setErrors((prev) => ({ setErrors((prev) => ({
...prev, ...prev,
[field]: "", [field]: '',
})); }))
}
} }
};
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault()
if (!validateForm()) { if (!validateForm()) {
return; return
} }
setSaving(true); setSaving(true)
try { try {
// Simulate API call // Simulate API call
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000))
console.log("Employee data:", { console.log('Employee data:', {
...formData, ...formData,
id: isEdit ? id : undefined, id: isEdit ? id : undefined,
}); })
// Show success message // Show success message
alert( alert(isEdit ? 'Personel başarıyla güncellendi!' : 'Personel başarıyla oluşturuldu!')
isEdit
? "Personel başarıyla güncellendi!"
: "Personel başarıyla oluşturuldu!"
);
// Navigate back to list // Navigate back to list
navigate("/admin/hr"); navigate('/admin/hr')
} catch (error) { } catch (error) {
console.error("Error saving employee:", error); console.error('Error saving employee:', error)
alert("Bir hata oluştu. Lütfen tekrar deneyin."); alert('Bir hata oluştu. Lütfen tekrar deneyin.')
} finally { } finally {
setSaving(false); setSaving(false)
}
} }
};
const handleCancel = () => { const handleCancel = () => {
navigate("/admin/hr"); navigate('/admin/hr')
}; }
if (loading) { if (loading) {
return <LoadingSpinner />; return <LoadingSpinner />
} }
return ( return (
<div className="space-y-4 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">
{isEdit ? "Personel Düzenle" : "Yeni Personel"} {isEdit ? 'Personel Düzenle' : 'Yeni Personel'}
</h2> </h2>
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{isEdit {isEdit
? "Mevcut personel bilgilerini güncelleyin" ? 'Mevcut personel bilgilerini güncelleyin'
: "Yeni personel bilgilerini girin"} : 'Yeni personel bilgilerini girin'}
</p> </p>
</div> </div>
</div> </div>
@ -273,18 +268,16 @@ const EmployeeForm: React.FC = () => {
autoFocus autoFocus
type="text" type="text"
value={formData.code} value={formData.code}
onChange={(e) => handleInputChange("code", e.target.value)} onChange={(e) => handleInputChange('code', e.target.value)}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.code errors.code
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
placeholder="Örn: EMP001" placeholder="Örn: EMP001"
/> />
{errors.employeeCode && ( {errors.employeeCode && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.employeeCode}</p>
{errors.employeeCode}
</p>
)} )}
</div> </div>
@ -296,63 +289,49 @@ const EmployeeForm: React.FC = () => {
type="text" type="text"
maxLength={11} maxLength={11}
value={formData.nationalId} value={formData.nationalId}
onChange={(e) => onChange={(e) => handleInputChange('nationalId', e.target.value)}
handleInputChange("nationalId", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.nationalId errors.nationalId
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
placeholder="12345678901" placeholder="12345678901"
/> />
{errors.nationalId && ( {errors.nationalId && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.nationalId}</p>
{errors.nationalId}
</p>
)} )}
</div> </div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">Ad *</label>
Ad *
</label>
<input <input
type="text" type="text"
value={formData.firstName} value={formData.firstName}
onChange={(e) => onChange={(e) => handleInputChange('firstName', e.target.value)}
handleInputChange("firstName", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.firstName errors.firstName
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
placeholder="Ad" placeholder="Ad"
/> />
{errors.firstName && ( {errors.firstName && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.firstName}</p>
{errors.firstName}
</p>
)} )}
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">Soyad *</label>
Soyad *
</label>
<input <input
type="text" type="text"
value={formData.lastName} value={formData.lastName}
onChange={(e) => onChange={(e) => handleInputChange('lastName', e.target.value)}
handleInputChange("lastName", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.lastName errors.lastName
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
placeholder="Soyad" placeholder="Soyad"
/> />
@ -369,33 +348,24 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<input <input
type="date" type="date"
value={ value={formData.birthDate && formData.birthDate.toString().split('T')[0]}
formData.birthDate && onChange={(e) => handleInputChange('birthDate', e.target.value)}
formData.birthDate.toString().split("T")[0]
}
onChange={(e) =>
handleInputChange("birthDate", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.birthDate errors.birthDate
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
/> />
{errors.birthDate && ( {errors.birthDate && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.birthDate}</p>
{errors.birthDate}
</p>
)} )}
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">Cinsiyet</label>
Cinsiyet
</label>
<select <select
value={formData.gender} value={formData.gender}
onChange={(e) => handleInputChange("gender", e.target.value)} onChange={(e) => handleInputChange('gender', e.target.value)}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
> >
<option value="">Cinsiyet seçin</option> <option value="">Cinsiyet seçin</option>
@ -410,9 +380,7 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<select <select
value={formData.maritalStatus} value={formData.maritalStatus}
onChange={(e) => onChange={(e) => handleInputChange('maritalStatus', e.target.value)}
handleInputChange("maritalStatus", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
> >
<option value="">Medeni durum seçin</option> <option value="">Medeni durum seçin</option>
@ -438,26 +406,22 @@ const EmployeeForm: React.FC = () => {
<div className="p-3 space-y-3"> <div className="p-3 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3"> <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">Email *</label>
Email *
</label>
<div className="relative"> <div className="relative">
<FaEnvelope className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" /> <FaEnvelope className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<input <input
type="email" type="email"
value={formData.email} value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)} onChange={(e) => handleInputChange('email', e.target.value)}
className={`block w-full pl-10 pr-3 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full pl-10 pr-3 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.email errors.email
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
placeholder="email@company.com" placeholder="email@company.com"
/> />
</div> </div>
{errors.email && ( {errors.email && <p className="mt-1 text-sm text-red-600">{errors.email}</p>}
<p className="mt-1 text-sm text-red-600">{errors.email}</p>
)}
</div> </div>
<div> <div>
@ -467,7 +431,7 @@ const EmployeeForm: React.FC = () => {
<input <input
type="tel" type="tel"
value={formData.phone} value={formData.phone}
onChange={(e) => handleInputChange("phone", e.target.value)} onChange={(e) => handleInputChange('phone', e.target.value)}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="+90 212 555 0123" placeholder="+90 212 555 0123"
/> />
@ -480,9 +444,7 @@ const EmployeeForm: React.FC = () => {
<input <input
type="tel" type="tel"
value={formData.personalPhone} value={formData.personalPhone}
onChange={(e) => onChange={(e) => handleInputChange('personalPhone', e.target.value)}
handleInputChange("personalPhone", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="+90 532 555 0123" placeholder="+90 532 555 0123"
/> />
@ -518,17 +480,12 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<input <input
type="date" type="date"
value={ value={formData.hireDate && formData.hireDate.toString().split('T')[0]}
formData.hireDate && onChange={(e) => handleInputChange('hireDate', e.target.value)}
formData.hireDate.toString().split("T")[0]
}
onChange={(e) =>
handleInputChange("hireDate", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.hireDate errors.hireDate
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
/> />
{errors.hireDate && ( {errors.hireDate && (
@ -542,13 +499,11 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<select <select
value={formData.employmentType} value={formData.employmentType}
onChange={(e) => onChange={(e) => handleInputChange('employmentType', e.target.value)}
handleInputChange("employmentType", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.employmentType errors.employmentType
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
> >
<option value="">Tip seçin</option> <option value="">Tip seçin</option>
@ -559,9 +514,7 @@ const EmployeeForm: React.FC = () => {
<option value="TEMPORARY">Geçici</option> <option value="TEMPORARY">Geçici</option>
</select> </select>
{errors.employmentType && ( {errors.employmentType && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.employmentType}</p>
{errors.employmentType}
</p>
)} )}
</div> </div>
</div> </div>
@ -573,13 +526,11 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<select <select
value={formData.departmantId} value={formData.departmantId}
onChange={(e) => onChange={(e) => handleInputChange('departmantId', e.target.value)}
handleInputChange("departmantId", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.departmantId errors.departmantId
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
> >
<option value="">Departman seçin</option> <option value="">Departman seçin</option>
@ -590,25 +541,19 @@ const EmployeeForm: React.FC = () => {
))} ))}
</select> </select>
{errors.departmentId && ( {errors.departmentId && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.departmentId}</p>
{errors.departmentId}
</p>
)} )}
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">Pozisyon *</label>
Pozisyon *
</label>
<select <select
value={formData.jobPositionId} value={formData.jobPositionId}
onChange={(e) => onChange={(e) => handleInputChange('jobPositionId', e.target.value)}
handleInputChange("jobPositionId", e.target.value)
}
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.jobPositionId errors.jobPositionId
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
> >
<option value="">Pozisyon seçin</option> <option value="">Pozisyon seçin</option>
@ -619,21 +564,15 @@ const EmployeeForm: React.FC = () => {
))} ))}
</select> </select>
{errors.jobPositionId && ( {errors.jobPositionId && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.jobPositionId}</p>
{errors.jobPositionId}
</p>
)} )}
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">Yönetici</label>
Yönetici
</label>
<select <select
value={formData.managerId} value={formData.managerId}
onChange={(e) => onChange={(e) => handleInputChange('managerId', e.target.value)}
handleInputChange("managerId", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
> >
<option value="">Yönetici seçin</option> <option value="">Yönetici seçin</option>
@ -654,9 +593,7 @@ const EmployeeForm: React.FC = () => {
<input <input
type="text" type="text"
value={formData.workLocation} value={formData.workLocation}
onChange={(e) => onChange={(e) => handleInputChange('workLocation', e.target.value)}
handleInputChange("workLocation", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Ofis lokasyonu" placeholder="Ofis lokasyonu"
/> />
@ -669,9 +606,7 @@ const EmployeeForm: React.FC = () => {
<input <input
type="text" type="text"
value={formData.badgeNumber} value={formData.badgeNumber}
onChange={(e) => onChange={(e) => handleInputChange('badgeNumber', e.target.value)}
handleInputChange("badgeNumber", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Personel kartı numarası" placeholder="Personel kartı numarası"
/> />
@ -701,22 +636,17 @@ const EmployeeForm: React.FC = () => {
step="0.01" step="0.01"
value={formData.baseSalary} value={formData.baseSalary}
onChange={(e) => onChange={(e) =>
handleInputChange( handleInputChange('baseSalary', parseFloat(e.target.value) || 0)
"baseSalary",
parseFloat(e.target.value) || 0
)
} }
className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${ className={`block w-full px-2 py-1 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.baseSalary errors.baseSalary
? "border-red-300 focus:border-red-500 focus:ring-red-500" ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500" : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`} }`}
placeholder="0.00" placeholder="0.00"
/> />
{errors.baseSalary && ( {errors.baseSalary && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">{errors.baseSalary}</p>
{errors.baseSalary}
</p>
)} )}
</div> </div>
@ -726,9 +656,7 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<select <select
value={formData.currency} value={formData.currency}
onChange={(e) => onChange={(e) => handleInputChange('currency', e.target.value)}
handleInputChange("currency", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
> >
<option value="TRY">Türk Lirası (TRY)</option> <option value="TRY">Türk Lirası (TRY)</option>
@ -743,9 +671,7 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<select <select
value={formData.payrollGroup} value={formData.payrollGroup}
onChange={(e) => onChange={(e) => handleInputChange('payrollGroup', e.target.value)}
handleInputChange("payrollGroup", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
> >
<option value="">Grup seçin</option> <option value="">Grup seçin</option>
@ -776,9 +702,7 @@ const EmployeeForm: React.FC = () => {
<input <input
type="text" type="text"
value={formData.emergencyContact.name} value={formData.emergencyContact.name}
onChange={(e) => onChange={(e) => handleInputChange('emergencyContact.name', e.target.value)}
handleInputChange("emergencyContact.name", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Ad Soyad" placeholder="Ad Soyad"
/> />
@ -791,9 +715,7 @@ const EmployeeForm: React.FC = () => {
<input <input
type="tel" type="tel"
value={formData.emergencyContact.phone} value={formData.emergencyContact.phone}
onChange={(e) => onChange={(e) => handleInputChange('emergencyContact.phone', e.target.value)}
handleInputChange("emergencyContact.phone", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="+90 532 555 0123" placeholder="+90 532 555 0123"
/> />
@ -807,10 +729,7 @@ const EmployeeForm: React.FC = () => {
type="text" type="text"
value={formData.emergencyContact.relationship} value={formData.emergencyContact.relationship}
onChange={(e) => onChange={(e) =>
handleInputChange( handleInputChange('emergencyContact.relationship', e.target.value)
"emergencyContact.relationship",
e.target.value
)
} }
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Eş, Anne, Baba vs." placeholder="Eş, Anne, Baba vs."
@ -825,9 +744,7 @@ const EmployeeForm: React.FC = () => {
</label> </label>
<select <select
value={formData.employeeStatus} value={formData.employeeStatus}
onChange={(e) => onChange={(e) => handleInputChange('employeeStatus', e.target.value)}
handleInputChange("employeeStatus", e.target.value)
}
className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500" className="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
> >
<option value="ACTIVE">Aktif</option> <option value="ACTIVE">Aktif</option>
@ -842,15 +759,10 @@ const EmployeeForm: React.FC = () => {
type="checkbox" type="checkbox"
id="isActive" id="isActive"
checked={formData.isActive} checked={formData.isActive}
onChange={(e) => onChange={(e) => handleInputChange('isActive', e.target.checked)}
handleInputChange("isActive", e.target.checked)
}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/> />
<label <label htmlFor="isActive" className="ml-2 block text-sm text-gray-900">
htmlFor="isActive"
className="ml-2 block text-sm text-gray-900"
>
Aktif Aktif
</label> </label>
</div> </div>
@ -881,14 +793,15 @@ const EmployeeForm: React.FC = () => {
) : ( ) : (
<> <>
<FaSave className="w-4 h-4 mr-2" /> <FaSave className="w-4 h-4 mr-2" />
{isEdit ? "Güncelle" : "Kaydet"} {isEdit ? 'Güncelle' : 'Kaydet'}
</> </>
)} )}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
); </Container>
}; )
}
export default EmployeeForm; export default EmployeeForm

View file

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom'
import { useQuery } from "@tanstack/react-query"; import { useQuery } from '@tanstack/react-query'
import { import {
FaUsers, FaUsers,
FaPlus, FaPlus,
@ -18,83 +18,78 @@ import {
FaList, FaList,
FaTh, FaTh,
FaBriefcase, FaBriefcase,
} from "react-icons/fa"; } from 'react-icons/fa'
import classNames from "classnames"; import classNames from 'classnames'
import { EmployeeStatusEnum, HrEmployee } from "../../../types/hr"; import { EmployeeStatusEnum, HrEmployee } from '../../../types/hr'
import dayjs from "dayjs"; import dayjs from 'dayjs'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import EmployeeViewModal from "./EmployeeViewModal"; import EmployeeView from './EmployeeView'
import Widget from "../../../components/common/Widget"; import Widget from '../../../components/common/Widget'
import { import {
getEmploymentTypeColor, getEmploymentTypeColor,
getEmploymentTypeText, getEmploymentTypeText,
getEmployeeStatusColor, getEmployeeStatusColor,
getEmployeeStatusIcon, getEmployeeStatusIcon,
getEmployeeStatusText, getEmployeeStatusText,
} from "../../../utils/erp"; } from '../../../utils/erp'
import { Container } from '@/components/shared'
const EmployeeList: React.FC = () => { const EmployeeList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [filterStatus, setFilterStatus] = useState("all"); const [filterStatus, setFilterStatus] = useState('all')
const [filterDepartment, setFilterDepartment] = useState("all"); const [filterDepartment, setFilterDepartment] = useState('all')
const [showFilters, setShowFilters] = useState(false); const [showFilters, setShowFilters] = useState(false)
const [viewMode, setViewMode] = useState<"list" | "cards">("list"); const [viewMode, setViewMode] = useState<'list' | 'cards'>('list')
// Modal states // Modal states
const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false)
const [selectedEmployee, setSelectedEmployee] = useState<HrEmployee | null>( const [selectedEmployee, setSelectedEmployee] = useState<HrEmployee | null>(null)
null
);
const { const {
data: employees, data: employees,
isLoading, isLoading,
error, error,
} = useQuery({ } = useQuery({
queryKey: ["employees", searchTerm, filterStatus, filterDepartment], queryKey: ['employees', searchTerm, filterStatus, filterDepartment],
queryFn: async () => { queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500))
return mockEmployees.filter((employee) => { return mockEmployees.filter((employee) => {
const matchesSearch = const matchesSearch =
employee.code.toLowerCase().includes(searchTerm.toLowerCase()) || employee.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
employee.fullName.toLowerCase().includes(searchTerm.toLowerCase()) || employee.fullName.toLowerCase().includes(searchTerm.toLowerCase()) ||
employee.email.toLowerCase().includes(searchTerm.toLowerCase()); employee.email.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = const matchesStatus = filterStatus === 'all' || employee.employeeStatus === filterStatus
filterStatus === "all" || employee.employeeStatus === filterStatus;
const matchesDepartment = const matchesDepartment =
filterDepartment === "all" || filterDepartment === 'all' || employee.department?.code === filterDepartment
employee.department?.code === filterDepartment; return matchesSearch && matchesStatus && matchesDepartment
return matchesSearch && matchesStatus && matchesDepartment; })
});
}, },
}); })
// Modal handlers // Modal handlers
const handleViewEmployee = (employee: HrEmployee) => { const handleViewEmployee = (employee: HrEmployee) => {
setSelectedEmployee(employee); setSelectedEmployee(employee)
setIsViewModalOpen(true); setIsViewModalOpen(true)
}; }
const handleCloseViewModal = () => { const handleCloseViewModal = () => {
setIsViewModalOpen(false); setIsViewModalOpen(false)
setSelectedEmployee(null); setSelectedEmployee(null)
}; }
const handleEditFromView = (employee: HrEmployee) => { const handleEditFromView = (employee: HrEmployee) => {
setIsViewModalOpen(false); setIsViewModalOpen(false)
// Navigate to edit page - you can replace this with a modal if preferred // Navigate to edit page - you can replace this with a modal if preferred
window.location.href = `/admin/hr/employees/edit/${employee.id}`; window.location.href = `/admin/hr/employees/edit/${employee.id}`
}; }
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-3 text-gray-600"> <span className="ml-3 text-gray-600">Personel listesi yükleniyor...</span>
Personel listesi yükleniyor...
</span>
</div> </div>
); )
} }
if (error) { if (error) {
@ -102,16 +97,15 @@ const EmployeeList: React.FC = () => {
<div className="bg-red-50 border border-red-200 rounded-lg p-4"> <div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center"> <div className="flex items-center">
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" /> <FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
<span className="text-red-800"> <span className="text-red-800">Personel listesi yüklenirken hata oluştu.</span>
Personel listesi yüklenirken hata oluştu.
</span>
</div> </div>
</div> </div>
); )
} }
return ( return (
<div className="space-y-3 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
{/* Title & Description */} {/* Title & Description */}
@ -125,22 +119,22 @@ const EmployeeList: React.FC = () => {
{/* View Mode Toggle */} {/* View Mode Toggle */}
<div className="flex bg-gray-100 rounded-lg p-0.5"> <div className="flex bg-gray-100 rounded-lg p-0.5">
<button <button
onClick={() => setViewMode("list")} onClick={() => setViewMode('list')}
className={`p-1.5 rounded-md transition-colors ${ className={`p-1.5 rounded-md transition-colors ${
viewMode === "list" viewMode === 'list'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
title="Liste Görünümü" title="Liste Görünümü"
> >
<FaList className="w-4 h-4" /> <FaList className="w-4 h-4" />
</button> </button>
<button <button
onClick={() => setViewMode("cards")} onClick={() => setViewMode('cards')}
className={`p-1.5 rounded-md transition-colors ${ className={`p-1.5 rounded-md transition-colors ${
viewMode === "cards" viewMode === 'cards'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
title="Kart Görünümü" title="Kart Görünümü"
> >
@ -167,10 +161,10 @@ const EmployeeList: React.FC = () => {
<button <button
onClick={() => setShowFilters(!showFilters)} onClick={() => setShowFilters(!showFilters)}
className={classNames( className={classNames(
"flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors", 'flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors',
showFilters showFilters
? "border-blue-500 bg-blue-50 text-blue-700" ? 'border-blue-500 bg-blue-50 text-blue-700'
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50" : 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
)} )}
> >
<FaFilter size={16} className="mr-2" /> <FaFilter size={16} className="mr-2" />
@ -179,7 +173,7 @@ const EmployeeList: React.FC = () => {
{/* Export Button */} {/* Export Button */}
<button <button
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")} onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
className="flex items-center px-3 py-1 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors" className="flex items-center px-3 py-1 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
> >
<FaDownload size={16} className="mr-2" /> <FaDownload size={16} className="mr-2" />
@ -202,9 +196,7 @@ const EmployeeList: React.FC = () => {
<div className="bg-white border border-gray-200 rounded-lg p-3"> <div className="bg-white border border-gray-200 rounded-lg p-3">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3"> <div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div> <div>
<label className="block text-xs font-medium text-gray-700 mb-1"> <label className="block text-xs font-medium text-gray-700 mb-1">Durum</label>
Durum
</label>
<select <select
value={filterStatus} value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)} onChange={(e) => setFilterStatus(e.target.value)}
@ -215,16 +207,12 @@ const EmployeeList: React.FC = () => {
<option value={EmployeeStatusEnum.Inactive}>Pasif</option> <option value={EmployeeStatusEnum.Inactive}>Pasif</option>
<option value={EmployeeStatusEnum.OnLeave}>İzinli</option> <option value={EmployeeStatusEnum.OnLeave}>İzinli</option>
<option value={EmployeeStatusEnum.Suspended}>Askıda</option> <option value={EmployeeStatusEnum.Suspended}>Askıda</option>
<option value={EmployeeStatusEnum.Terminated}> <option value={EmployeeStatusEnum.Terminated}>İşten Çıkarıldı</option>
İşten Çıkarıldı
</option>
</select> </select>
</div> </div>
<div> <div>
<label className="block text-xs font-medium text-gray-700 mb-1"> <label className="block text-xs font-medium text-gray-700 mb-1">Departman</label>
Departman
</label>
<select <select
value={filterDepartment} value={filterDepartment}
onChange={(e) => setFilterDepartment(e.target.value)} onChange={(e) => setFilterDepartment(e.target.value)}
@ -241,9 +229,9 @@ const EmployeeList: React.FC = () => {
<div className="flex items-end"> <div className="flex items-end">
<button <button
onClick={() => { onClick={() => {
setFilterStatus("all"); setFilterStatus('all')
setFilterDepartment("all"); setFilterDepartment('all')
setSearchTerm(""); setSearchTerm('')
}} }}
className="w-full px-3 py-1 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors" className="w-full px-3 py-1 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
> >
@ -266,9 +254,7 @@ const EmployeeList: React.FC = () => {
<Widget <Widget
title="Aktif Personel" title="Aktif Personel"
value={ value={
employees?.filter( employees?.filter((e) => e.employeeStatus === EmployeeStatusEnum.Active).length || 0
(e) => e.employeeStatus === EmployeeStatusEnum.Active
).length || 0
} }
color="green" color="green"
icon="FaCheckCircle" icon="FaCheckCircle"
@ -277,9 +263,7 @@ const EmployeeList: React.FC = () => {
<Widget <Widget
title="İzinli Personel" title="İzinli Personel"
value={ value={
employees?.filter( employees?.filter((e) => e.employeeStatus === EmployeeStatusEnum.OnLeave).length || 0
(e) => e.employeeStatus === EmployeeStatusEnum.OnLeave
).length || 0
} }
color="yellow" color="yellow"
icon="FaCalendar" icon="FaCalendar"
@ -288,9 +272,8 @@ const EmployeeList: React.FC = () => {
<Widget <Widget
title="Yeni İşe Alınanlar" title="Yeni İşe Alınanlar"
value={ value={
employees?.filter((e) => employees?.filter((e) => dayjs(e.hireDate).isAfter(dayjs().subtract(30, 'day')))
dayjs(e.hireDate).isAfter(dayjs().subtract(30, "day")) .length || 0
).length || 0
} }
color="purple" color="purple"
icon="FaArrowUp" icon="FaArrowUp"
@ -298,12 +281,10 @@ const EmployeeList: React.FC = () => {
</div> </div>
{/* Employees Display */} {/* Employees Display */}
{viewMode === "list" ? ( {viewMode === 'list' ? (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 text-xs"> <div className="bg-white rounded-lg shadow-sm border border-gray-200 text-xs">
<div className="px-3 py-2 border-b border-gray-200"> <div className="px-3 py-2 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">Personel Listesi</h2>
Personel Listesi
</h2>
</div> </div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@ -336,10 +317,7 @@ const EmployeeList: React.FC = () => {
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{employees?.map((employee) => ( {employees?.map((employee) => (
<tr <tr key={employee.id} className="hover:bg-gray-50 transition-colors text-xs">
key={employee.id}
className="hover:bg-gray-50 transition-colors text-xs"
>
<td className="px-3 py-2"> <td className="px-3 py-2">
<div className="flex items-center"> <div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10"> <div className="flex-shrink-0 h-10 w-10">
@ -348,12 +326,8 @@ const EmployeeList: React.FC = () => {
</div> </div>
</div> </div>
<div className="ml-4"> <div className="ml-4">
<div className="text-xs font-medium text-gray-900"> <div className="text-xs font-medium text-gray-900">{employee.code}</div>
{employee.code} <div className="text-xs text-gray-500">{employee.fullName}</div>
</div>
<div className="text-xs text-gray-500">
{employee.fullName}
</div>
{employee.badgeNumber && ( {employee.badgeNumber && (
<div className="text-xs text-gray-400 mt-1"> <div className="text-xs text-gray-400 mt-1">
Rozet: {employee.badgeNumber} Rozet: {employee.badgeNumber}
@ -387,9 +361,7 @@ const EmployeeList: React.FC = () => {
<FaBuilding size={14} className="mr-1" /> <FaBuilding size={14} className="mr-1" />
{employee.department?.name} {employee.department?.name}
</div> </div>
<div className="text-xs text-gray-400 mt-1"> <div className="text-xs text-gray-400 mt-1">{employee.workLocation}</div>
{employee.workLocation}
</div>
</div> </div>
</td> </td>
@ -397,18 +369,18 @@ const EmployeeList: React.FC = () => {
<div className="space-y-1"> <div className="space-y-1">
<div <div
className={classNames( className={classNames(
"text-xs font-medium", 'text-xs font-medium',
getEmploymentTypeColor(employee.employmentType) getEmploymentTypeColor(employee.employmentType),
)} )}
> >
{getEmploymentTypeText(employee.employmentType)} {getEmploymentTypeText(employee.employmentType)}
</div> </div>
<div className="flex items-center text-xs text-gray-500"> <div className="flex items-center text-xs text-gray-500">
<FaCalendar size={14} className="mr-1" /> <FaCalendar size={14} className="mr-1" />
{dayjs(employee.hireDate).format("DD.MM.YYYY")} {dayjs(employee.hireDate).format('DD.MM.YYYY')}
</div> </div>
<div className="text-xs text-gray-400"> <div className="text-xs text-gray-400">
{dayjs().diff(employee.hireDate, "year")} yıl deneyim {dayjs().diff(employee.hireDate, 'year')} yıl deneyim
</div> </div>
</div> </div>
</td> </td>
@ -417,16 +389,14 @@ const EmployeeList: React.FC = () => {
<div className="text-xs font-medium text-gray-900"> <div className="text-xs font-medium text-gray-900">
{employee.baseSalary.toLocaleString()} {employee.baseSalary.toLocaleString()}
</div> </div>
<div className="text-xs text-gray-500"> <div className="text-xs text-gray-500">{employee.payrollGroup}</div>
{employee.payrollGroup}
</div>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<span <span
className={classNames( className={classNames(
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium", 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
getEmployeeStatusColor(employee.employeeStatus) getEmployeeStatusColor(employee.employeeStatus),
)} )}
> >
{getEmployeeStatusIcon(employee.employeeStatus)} {getEmployeeStatusIcon(employee.employeeStatus)}
@ -456,9 +426,7 @@ const EmployeeList: React.FC = () => {
<button <button
onClick={() => onClick={() =>
alert( alert('Performans değerlendirme özelliği yakında eklenecek')
"Performans değerlendirme özelliği yakında eklenecek"
)
} }
className="p-1 text-gray-600 hover:text-green-600 hover:bg-green-50 rounded-lg transition-colors" className="p-1 text-gray-600 hover:text-green-600 hover:bg-green-50 rounded-lg transition-colors"
title="Performans Değerlendirmesi" title="Performans Değerlendirmesi"
@ -476,12 +444,8 @@ const EmployeeList: React.FC = () => {
{(!employees || employees.length === 0) && ( {(!employees || employees.length === 0) && (
<div className="text-center py-12"> <div className="text-center py-12">
<FaUsers className="mx-auto h-10 w-10 text-gray-400" /> <FaUsers className="mx-auto h-10 w-10 text-gray-400" />
<h3 className="mt-2 text-xs font-medium text-gray-900"> <h3 className="mt-2 text-xs font-medium text-gray-900">Personel bulunamadı</h3>
Personel bulunamadı <p className="mt-1 text-xs text-gray-500">Yeni personel ekleyerek başlayın.</p>
</h3>
<p className="mt-1 text-xs text-gray-500">
Yeni personel ekleyerek başlayın.
</p>
<div className="mt-6"> <div className="mt-6">
<Link <Link
to="/admin/hr/employees/new" to="/admin/hr/employees/new"
@ -518,14 +482,12 @@ const EmployeeList: React.FC = () => {
<p className="text-sm text-gray-500">{employee.code}</p> <p className="text-sm text-gray-500">{employee.code}</p>
<span <span
className={classNames( className={classNames(
"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium mt-1", 'inline-flex items-center px-2 py-1 rounded-full text-xs font-medium mt-1',
getEmployeeStatusColor(employee.employeeStatus) getEmployeeStatusColor(employee.employeeStatus),
)} )}
> >
{getEmployeeStatusIcon(employee.employeeStatus)} {getEmployeeStatusIcon(employee.employeeStatus)}
<span className="ml-1"> <span className="ml-1">{getEmployeeStatusText(employee.employeeStatus)}</span>
{getEmployeeStatusText(employee.employeeStatus)}
</span>
</span> </span>
</div> </div>
</div> </div>
@ -534,16 +496,12 @@ const EmployeeList: React.FC = () => {
<div className="space-y-1.5"> <div className="space-y-1.5">
<div className="flex items-center text-sm text-gray-600"> <div className="flex items-center text-sm text-gray-600">
<FaBriefcase className="w-4 h-4 mr-2 text-gray-400" /> <FaBriefcase className="w-4 h-4 mr-2 text-gray-400" />
<span> <span>{employee.jobPosition?.name || 'Pozisyon belirtilmemiş'}</span>
{employee.jobPosition?.name || "Pozisyon belirtilmemiş"}
</span>
</div> </div>
<div className="flex items-center text-sm text-gray-600"> <div className="flex items-center text-sm text-gray-600">
<FaBuilding className="w-4 h-4 mr-2 text-gray-400" /> <FaBuilding className="w-4 h-4 mr-2 text-gray-400" />
<span> <span>{employee.department?.name || 'Departman belirtilmemiş'}</span>
{employee.department?.name || "Departman belirtilmemiş"}
</span>
</div> </div>
<div className="flex items-center text-sm text-gray-600"> <div className="flex items-center text-sm text-gray-600">
@ -560,14 +518,11 @@ const EmployeeList: React.FC = () => {
<div className="flex items-center text-sm text-gray-600"> <div className="flex items-center text-sm text-gray-600">
<FaCalendar className="w-4 h-4 mr-2 text-gray-400" /> <FaCalendar className="w-4 h-4 mr-2 text-gray-400" />
<span> <span>İşe Başlama: {dayjs(employee.hireDate).format('DD.MM.YYYY')}</span>
İşe Başlama: {dayjs(employee.hireDate).format("DD.MM.YYYY")}
</span>
</div> </div>
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600">
<span className="font-medium">İstihdam Türü:</span>{" "} <span className="font-medium">İstihdam Türü:</span> {employee.employmentType}
{employee.employmentType}
</div> </div>
</div> </div>
@ -588,9 +543,7 @@ const EmployeeList: React.FC = () => {
<FaEdit className="w-4 h-4" /> <FaEdit className="w-4 h-4" />
</Link> </Link>
<button <button
onClick={() => onClick={() => alert('Performans değerlendirme özelliği yakında eklenecek')}
alert("Performans değerlendirme özelliği yakında eklenecek")
}
className="p-1 text-orange-600 hover:bg-orange-50 rounded-lg transition-colors" className="p-1 text-orange-600 hover:bg-orange-50 rounded-lg transition-colors"
title="Performans" title="Performans"
> >
@ -605,12 +558,8 @@ const EmployeeList: React.FC = () => {
{(!employees || employees.length === 0) && ( {(!employees || employees.length === 0) && (
<div className="text-center py-12"> <div className="text-center py-12">
<FaUsers className="mx-auto h-10 w-10 text-gray-400" /> <FaUsers className="mx-auto h-10 w-10 text-gray-400" />
<h3 className="mt-2 text-xs font-medium text-gray-900"> <h3 className="mt-2 text-xs font-medium text-gray-900">Personel bulunamadı</h3>
Personel bulunamadı <p className="mt-1 text-xs text-gray-500">Yeni personel ekleyerek başlayın.</p>
</h3>
<p className="mt-1 text-xs text-gray-500">
Yeni personel ekleyerek başlayın.
</p>
<div className="mt-6"> <div className="mt-6">
<Link <Link
to="/admin/hr/employees/new" to="/admin/hr/employees/new"
@ -622,16 +571,17 @@ const EmployeeList: React.FC = () => {
</div> </div>
</div> </div>
)} )}
</div>
{/* Employee View Modal */} {/* Employee View Modal */}
<EmployeeViewModal <EmployeeView
isOpen={isViewModalOpen} isOpen={isViewModalOpen}
onClose={handleCloseViewModal} onClose={handleCloseViewModal}
employee={selectedEmployee} employee={selectedEmployee}
onEdit={handleEditFromView} onEdit={handleEditFromView}
/> />
</div> </Container>
); )
}; }
export default EmployeeList; export default EmployeeList

View file

@ -1,77 +1,497 @@
import React, { useState, useEffect } from "react"; import React from "react";
import { useParams, useNavigate } from "react-router-dom"; import {
FaTimes,
FaUser,
FaIdCard,
FaEnvelope,
FaPhone,
FaCalendar,
FaBuilding,
FaBriefcase,
FaDollarSign,
FaMapMarkerAlt,
FaUserShield,
FaHeartbeat,
FaEdit,
FaClock,
FaBirthdayCake,
FaGraduationCap,
FaAward,
FaHistory,
} from "react-icons/fa";
import { HrEmployee } from "../../../types/hr"; import { HrEmployee } from "../../../types/hr";
import { mockEmployees } from "../../../mocks/mockEmployees"; import {
import EmployeeViewModal from "./EmployeeViewModal"; getEmployeeStatusColor,
import LoadingSpinner from "../../../components/common/LoadingSpinner"; getEmployeeStatusIcon,
getEmployeeStatusText,
} from "../../../utils/erp";
const EmployeeView: React.FC = () => { interface EmployeeViewModalProps {
const { id } = useParams<{ id: string }>(); isOpen: boolean;
const navigate = useNavigate(); onClose: () => void;
const [employee, setEmployee] = useState<HrEmployee | null>(null); employee: HrEmployee | null;
const [loading, setLoading] = useState(true); onEdit?: (employee: HrEmployee) => void;
}
useEffect(() => { const EmployeeViewModal: React.FC<EmployeeViewModalProps> = ({
const loadEmployee = async () => { isOpen,
setLoading(true); onClose,
// Simulate API call employee,
await new Promise((resolve) => setTimeout(resolve, 300)); onEdit,
}) => {
if (!isOpen || !employee) return null;
const foundEmployee = mockEmployees.find((emp) => emp.id === id); const formatDate = (date: Date | string): string => {
setEmployee(foundEmployee || null); return new Date(date).toLocaleDateString("tr-TR");
setLoading(false);
}; };
if (id) { const calculateWorkDuration = (hireDate: Date): string => {
loadEmployee(); const now = new Date();
const hire = new Date(hireDate);
const diffTime = Math.abs(now.getTime() - hire.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const years = Math.floor(diffDays / 365);
const months = Math.floor((diffDays % 365) / 30);
if (years > 0) {
return `${years} yıl ${months} ay`;
} }
}, [id]); return `${months} ay`;
const handleClose = () => {
navigate("/admin/hr/employees");
}; };
const handleEdit = (employee: HrEmployee) => {
navigate(`/admin/hr/employees/edit/${employee.id}`);
};
if (loading) {
return ( return (
<div className="min-h-screen flex items-center justify-center"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<LoadingSpinner /> <div className="bg-white rounded-lg shadow-2xl w-full max-w-4xl max-h-[95vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="bg-gradient-to-r from-blue-600 to-indigo-700 text-white p-3 flex-shrink-0">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
<FaUser className="w-5 h-5 text-white" />
</div> </div>
); <div>
} <h2 className="text-lg font-bold">{employee.fullName}</h2>
<p className="text-blue-100 text-sm">
if (!employee) { {employee.jobPosition?.name || "Pozisyon Belirtilmemiş"}
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2 className="text-xl font-bold text-gray-900 mb-1">
Personel Bulunamadı
</h2>
<p className="text-gray-600 mb-3">
Aradığınız personel kaydı bulunamadı.
</p> </p>
<button <p className="text-blue-200 text-xs">{employee.code}</p>
onClick={handleClose} </div>
className="px-3 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors text-sm" </div>
<div className="flex items-center space-x-2">
<div
className={`px-2 py-0.5 rounded-full border flex items-center space-x-1.5 text-xs ${getEmployeeStatusColor(
employee.employeeStatus
)} bg-opacity-90`}
> >
Personel Listesine Dön {getEmployeeStatusIcon(employee.employeeStatus)}
<span className="font-medium text-xs">
{getEmployeeStatusText(employee.employeeStatus)}
</span>
</div>
{onEdit && (
<button
onClick={() => onEdit(employee)}
className="bg-white bg-opacity-20 hover:bg-opacity-30 text-white px-2 py-1 text-xs rounded-md flex items-center space-x-1.5 transition-all"
>
<FaEdit className="w-3 h-3" />
<span>Düzenle</span>
</button>
)}
<button
onClick={onClose}
className="text-white hover:bg-white hover:bg-opacity-20 p-1 rounded-lg transition-all"
>
<FaTimes className="w-5 h-5" />
</button> </button>
</div> </div>
</div> </div>
); </div>
}
return ( {/* Content */}
<EmployeeViewModal <div className="p-3 overflow-y-auto flex-grow">
isOpen={true} <div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
onClose={handleClose} {/* Left Column - Personal Info */}
employee={employee} <div className="space-y-3">
onEdit={handleEdit} {/* Kişisel Bilgiler */}
/> <div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaIdCard className="w-4 h-4 text-blue-600 mr-2" />
Kişisel Bilgiler
</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<FaBirthdayCake className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Doğum Tarihi</p>
<p className="font-medium text-sm">
{formatDate(employee.birthDate)}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaUser className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Cinsiyet</p>
<p className="font-medium text-sm">{employee.gender}</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaHeartbeat className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Medeni Durum</p>
<p className="font-medium text-sm">
{employee.maritalStatus}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaIdCard className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">TC Kimlik No</p>
<p className="font-medium text-sm">
{employee.nationalId}
</p>
</div>
</div>
</div>
</div>
{/* İletişim Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaPhone className="w-4 h-4 text-green-600 mr-2" />
İletişim Bilgileri
</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<FaEnvelope className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">E-posta</p>
<p className="font-medium text-sm text-blue-600">
{employee.email}
</p>
</div>
</div>
{employee.phone && (
<div className="flex items-center space-x-2">
<FaPhone className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">İş Telefonu</p>
<p className="font-medium text-sm">{employee.phone}</p>
</div>
</div>
)}
{employee.personalPhone && (
<div className="flex items-center space-x-2">
<FaPhone className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Kişisel Telefon</p>
<p className="font-medium text-sm">
{employee.personalPhone}
</p>
</div>
</div>
)}
{employee.address && (
<div className="flex items-start space-x-2">
<FaMapMarkerAlt className="w-3 h-3 text-gray-500 mt-0.5" />
<div>
<p className="text-xs text-gray-600">Adres</p>
<p className="font-medium text-sm">
{employee.address.street}
<br />
{employee.address.city}, {employee.address.state}
<br />
{employee.address.postalCode},{" "}
{employee.address.country}
</p>
</div>
</div>
)}
</div>
</div>
{/* Acil Durum İletişim */}
{employee.emergencyContact && (
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaUserShield className="w-4 h-4 text-red-600 mr-2" />
Acil Durum İletişim
</h3>
<div className="space-y-2">
<div>
<p className="text-xs text-gray-600">İsim</p>
<p className="font-medium text-sm">
{employee.emergencyContact.name}
</p>
</div>
<div>
<p className="text-xs text-gray-600">Telefon</p>
<p className="font-medium text-sm">
{employee.emergencyContact.phone}
</p>
</div>
<div>
<p className="text-xs text-gray-600">Yakınlık</p>
<p className="font-medium text-sm">
{employee.emergencyContact.relationship}
</p>
</div>
</div>
</div>
)}
</div>
{/* Middle Column - Work Info */}
<div className="space-y-3">
{/* İş Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaBriefcase className="w-4 h-4 text-purple-600 mr-2" />
İş Bilgileri
</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<FaBuilding className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Departman</p>
<p className="font-medium text-sm">
{employee.department?.name || "Belirtilmemiş"}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaGraduationCap className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Pozisyon</p>
<p className="font-medium text-sm">
{employee.jobPosition?.name || "Belirtilmemiş"}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaCalendar className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">
İşe Başlama Tarihi
</p>
<p className="font-medium text-sm">
{formatDate(employee.hireDate)}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaHistory className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Çalışma Süresi</p>
<p className="font-medium text-sm">
{calculateWorkDuration(employee.hireDate)}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaBriefcase className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Çalışma Türü</p>
<p className="font-medium text-sm">
{employee.employmentType}
</p>
</div>
</div>
{employee.workLocation && (
<div className="flex items-center space-x-2">
<FaMapMarkerAlt className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">
Çalışma Lokasyonu
</p>
<p className="font-medium text-sm">
{employee.workLocation}
</p>
</div>
</div>
)}
{employee.badgeNumber && (
<div className="flex items-center space-x-2">
<FaIdCard className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Rozet Numarası</p>
<p className="font-medium text-sm">
{employee.badgeNumber}
</p>
</div>
</div>
)}
</div>
</div>
{/* Yönetici Bilgileri */}
{employee.manager && (
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaUserShield className="w-4 h-4 text-indigo-600 mr-2" />
Yönetici
</h3>
<div className="flex items-center space-x-2">
<div className="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
<FaUser className="w-5 h-5 text-indigo-600" />
</div>
<div>
<p className="font-medium text-sm text-gray-900">
{employee.manager.fullName}
</p>
<p className="text-xs text-gray-600">
{employee.manager.jobPosition?.name}
</p>
<p className="text-sm text-gray-600">
{employee.manager.email}
</p>
</div>
</div>
</div>
)}
{/* Performans ve Eğitimler */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaAward className="w-4 h-4 text-amber-600 mr-2" />
Performans & Gelişim
</h3>
<div className="grid grid-cols-2 gap-3">
<div className="text-center p-1.5 bg-white rounded-md">
<p className="text-xl font-bold text-amber-600">
{employee.evaluations?.length || 0}
</p>
<p className="text-xs text-gray-600">Değerlendirme</p>
</div>
<div className="text-center p-1.5 bg-white rounded-md">
<p className="text-xl font-bold text-emerald-600">
{employee.trainings?.length || 0}
</p>
<p className="text-xs text-gray-600">Eğitim</p>
</div>
</div>
</div>
</div>
{/* Right Column - Financial & Additional Info */}
<div className="space-y-3">
{/* Maaş Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaDollarSign className="w-4 h-4 text-emerald-600 mr-2" />
Maaş Bilgileri
</h3>
<div className="space-y-2">
<div>
<p className="text-xs text-gray-600">Temel Maaş</p>
<p className="text-xl font-bold text-emerald-600">
{employee.baseSalary?.toLocaleString()}{" "}
{employee.currency || "TRY"}
</p>
</div>
{employee.payrollGroup && (
<div>
<p className="text-xs text-gray-600">Bordro Grubu</p>
<p className="font-medium text-sm">
{employee.payrollGroup}
</p>
</div>
)}
</div>
</div>
{/* Banka Bilgileri */}
{employee.bankAccount && (
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaDollarSign className="w-4 h-4 text-slate-600 mr-2" />
Banka Bilgileri
</h3>
<div className="space-y-2">
<div>
<p className="text-xs text-gray-600">Banka Adı</p>
<p className="font-medium text-sm">
{employee.bankAccount.bankName}
</p>
</div>
<div>
<p className="text-xs text-gray-600">Hesap Numarası</p>
<p className="font-mono text-xs">
{employee.bankAccount.accountNumber}
</p>
</div>
<div>
<p className="text-xs text-gray-600">IBAN</p>
<p className="font-mono text-xs">
{employee.bankAccount.iban}
</p>
</div>
</div>
</div>
)}
{/* İzin Durumu */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaCalendar className="w-4 h-4 text-sky-600 mr-2" />
İzin Durumu
</h3>
<div className="grid grid-cols-1 gap-2">
<div className="bg-white p-1.5 rounded-md flex justify-between items-center">
<span className="text-xs text-gray-600">Toplam İzin</span>
<span className="font-bold text-sm text-sky-600">
{employee.leaves?.length || 0}
</span>
</div>
<div className="bg-white p-1.5 rounded-md flex justify-between items-center">
<span className="text-xs text-gray-600">Kalan İzin</span>
<span className="font-bold text-sm text-emerald-600">
15 gün
</span>
</div>
</div>
</div>
{/* Sistem Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaClock className="w-4 h-4 text-gray-600 mr-2" />
Sistem Bilgileri
</h3>
<div className="space-y-2 text-xs">
<div>
<p className="text-gray-600">Oluşturulma Tarihi</p>
<p className="font-medium text-gray-800">
{formatDate(employee.creationTime)}
</p>
</div>
<div>
<p className="text-gray-600">Son Güncelleme</p>
<p className="font-medium text-gray-800">
{formatDate(employee.lastModificationTime)}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="bg-gray-50 px-3 py-2 border-t flex-shrink-0">
<div className="flex justify-end">
<button
onClick={onClose}
className="px-3 py-1 text-sm bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors"
>
Kapat
</button>
</div>
</div>
</div>
</div>
); );
}; };
export default EmployeeView; export default EmployeeViewModal;

View file

@ -1,497 +0,0 @@
import React from "react";
import {
FaTimes,
FaUser,
FaIdCard,
FaEnvelope,
FaPhone,
FaCalendar,
FaBuilding,
FaBriefcase,
FaDollarSign,
FaMapMarkerAlt,
FaUserShield,
FaHeartbeat,
FaEdit,
FaClock,
FaBirthdayCake,
FaGraduationCap,
FaAward,
FaHistory,
} from "react-icons/fa";
import { HrEmployee } from "../../../types/hr";
import {
getEmployeeStatusColor,
getEmployeeStatusIcon,
getEmployeeStatusText,
} from "../../../utils/erp";
interface EmployeeViewModalProps {
isOpen: boolean;
onClose: () => void;
employee: HrEmployee | null;
onEdit?: (employee: HrEmployee) => void;
}
const EmployeeViewModal: React.FC<EmployeeViewModalProps> = ({
isOpen,
onClose,
employee,
onEdit,
}) => {
if (!isOpen || !employee) return null;
const formatDate = (date: Date | string): string => {
return new Date(date).toLocaleDateString("tr-TR");
};
const calculateWorkDuration = (hireDate: Date): string => {
const now = new Date();
const hire = new Date(hireDate);
const diffTime = Math.abs(now.getTime() - hire.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const years = Math.floor(diffDays / 365);
const months = Math.floor((diffDays % 365) / 30);
if (years > 0) {
return `${years} yıl ${months} ay`;
}
return `${months} ay`;
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-lg shadow-2xl w-full max-w-4xl max-h-[95vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="bg-gradient-to-r from-blue-600 to-indigo-700 text-white p-3 flex-shrink-0">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
<FaUser className="w-5 h-5 text-white" />
</div>
<div>
<h2 className="text-lg font-bold">{employee.fullName}</h2>
<p className="text-blue-100 text-sm">
{employee.jobPosition?.name || "Pozisyon Belirtilmemiş"}
</p>
<p className="text-blue-200 text-xs">{employee.code}</p>
</div>
</div>
<div className="flex items-center space-x-2">
<div
className={`px-2 py-0.5 rounded-full border flex items-center space-x-1.5 text-xs ${getEmployeeStatusColor(
employee.employeeStatus
)} bg-opacity-90`}
>
{getEmployeeStatusIcon(employee.employeeStatus)}
<span className="font-medium text-xs">
{getEmployeeStatusText(employee.employeeStatus)}
</span>
</div>
{onEdit && (
<button
onClick={() => onEdit(employee)}
className="bg-white bg-opacity-20 hover:bg-opacity-30 text-white px-2 py-1 text-xs rounded-md flex items-center space-x-1.5 transition-all"
>
<FaEdit className="w-3 h-3" />
<span>Düzenle</span>
</button>
)}
<button
onClick={onClose}
className="text-white hover:bg-white hover:bg-opacity-20 p-1 rounded-lg transition-all"
>
<FaTimes className="w-5 h-5" />
</button>
</div>
</div>
</div>
{/* Content */}
<div className="p-3 overflow-y-auto flex-grow">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
{/* Left Column - Personal Info */}
<div className="space-y-3">
{/* Kişisel Bilgiler */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaIdCard className="w-4 h-4 text-blue-600 mr-2" />
Kişisel Bilgiler
</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<FaBirthdayCake className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Doğum Tarihi</p>
<p className="font-medium text-sm">
{formatDate(employee.birthDate)}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaUser className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Cinsiyet</p>
<p className="font-medium text-sm">{employee.gender}</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaHeartbeat className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Medeni Durum</p>
<p className="font-medium text-sm">
{employee.maritalStatus}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaIdCard className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">TC Kimlik No</p>
<p className="font-medium text-sm">
{employee.nationalId}
</p>
</div>
</div>
</div>
</div>
{/* İletişim Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaPhone className="w-4 h-4 text-green-600 mr-2" />
İletişim Bilgileri
</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<FaEnvelope className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">E-posta</p>
<p className="font-medium text-sm text-blue-600">
{employee.email}
</p>
</div>
</div>
{employee.phone && (
<div className="flex items-center space-x-2">
<FaPhone className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">İş Telefonu</p>
<p className="font-medium text-sm">{employee.phone}</p>
</div>
</div>
)}
{employee.personalPhone && (
<div className="flex items-center space-x-2">
<FaPhone className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Kişisel Telefon</p>
<p className="font-medium text-sm">
{employee.personalPhone}
</p>
</div>
</div>
)}
{employee.address && (
<div className="flex items-start space-x-2">
<FaMapMarkerAlt className="w-3 h-3 text-gray-500 mt-0.5" />
<div>
<p className="text-xs text-gray-600">Adres</p>
<p className="font-medium text-sm">
{employee.address.street}
<br />
{employee.address.city}, {employee.address.state}
<br />
{employee.address.postalCode},{" "}
{employee.address.country}
</p>
</div>
</div>
)}
</div>
</div>
{/* Acil Durum İletişim */}
{employee.emergencyContact && (
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaUserShield className="w-4 h-4 text-red-600 mr-2" />
Acil Durum İletişim
</h3>
<div className="space-y-2">
<div>
<p className="text-xs text-gray-600">İsim</p>
<p className="font-medium text-sm">
{employee.emergencyContact.name}
</p>
</div>
<div>
<p className="text-xs text-gray-600">Telefon</p>
<p className="font-medium text-sm">
{employee.emergencyContact.phone}
</p>
</div>
<div>
<p className="text-xs text-gray-600">Yakınlık</p>
<p className="font-medium text-sm">
{employee.emergencyContact.relationship}
</p>
</div>
</div>
</div>
)}
</div>
{/* Middle Column - Work Info */}
<div className="space-y-3">
{/* İş Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaBriefcase className="w-4 h-4 text-purple-600 mr-2" />
İş Bilgileri
</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<FaBuilding className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Departman</p>
<p className="font-medium text-sm">
{employee.department?.name || "Belirtilmemiş"}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaGraduationCap className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Pozisyon</p>
<p className="font-medium text-sm">
{employee.jobPosition?.name || "Belirtilmemiş"}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaCalendar className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">
İşe Başlama Tarihi
</p>
<p className="font-medium text-sm">
{formatDate(employee.hireDate)}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaHistory className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Çalışma Süresi</p>
<p className="font-medium text-sm">
{calculateWorkDuration(employee.hireDate)}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<FaBriefcase className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Çalışma Türü</p>
<p className="font-medium text-sm">
{employee.employmentType}
</p>
</div>
</div>
{employee.workLocation && (
<div className="flex items-center space-x-2">
<FaMapMarkerAlt className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">
Çalışma Lokasyonu
</p>
<p className="font-medium text-sm">
{employee.workLocation}
</p>
</div>
</div>
)}
{employee.badgeNumber && (
<div className="flex items-center space-x-2">
<FaIdCard className="w-3 h-3 text-gray-500" />
<div>
<p className="text-xs text-gray-600">Rozet Numarası</p>
<p className="font-medium text-sm">
{employee.badgeNumber}
</p>
</div>
</div>
)}
</div>
</div>
{/* Yönetici Bilgileri */}
{employee.manager && (
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaUserShield className="w-4 h-4 text-indigo-600 mr-2" />
Yönetici
</h3>
<div className="flex items-center space-x-2">
<div className="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
<FaUser className="w-5 h-5 text-indigo-600" />
</div>
<div>
<p className="font-medium text-sm text-gray-900">
{employee.manager.fullName}
</p>
<p className="text-xs text-gray-600">
{employee.manager.jobPosition?.name}
</p>
<p className="text-sm text-gray-600">
{employee.manager.email}
</p>
</div>
</div>
</div>
)}
{/* Performans ve Eğitimler */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaAward className="w-4 h-4 text-amber-600 mr-2" />
Performans & Gelişim
</h3>
<div className="grid grid-cols-2 gap-3">
<div className="text-center p-1.5 bg-white rounded-md">
<p className="text-xl font-bold text-amber-600">
{employee.evaluations?.length || 0}
</p>
<p className="text-xs text-gray-600">Değerlendirme</p>
</div>
<div className="text-center p-1.5 bg-white rounded-md">
<p className="text-xl font-bold text-emerald-600">
{employee.trainings?.length || 0}
</p>
<p className="text-xs text-gray-600">Eğitim</p>
</div>
</div>
</div>
</div>
{/* Right Column - Financial & Additional Info */}
<div className="space-y-3">
{/* Maaş Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaDollarSign className="w-4 h-4 text-emerald-600 mr-2" />
Maaş Bilgileri
</h3>
<div className="space-y-2">
<div>
<p className="text-xs text-gray-600">Temel Maaş</p>
<p className="text-xl font-bold text-emerald-600">
{employee.baseSalary?.toLocaleString()}{" "}
{employee.currency || "TRY"}
</p>
</div>
{employee.payrollGroup && (
<div>
<p className="text-xs text-gray-600">Bordro Grubu</p>
<p className="font-medium text-sm">
{employee.payrollGroup}
</p>
</div>
)}
</div>
</div>
{/* Banka Bilgileri */}
{employee.bankAccount && (
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaDollarSign className="w-4 h-4 text-slate-600 mr-2" />
Banka Bilgileri
</h3>
<div className="space-y-2">
<div>
<p className="text-xs text-gray-600">Banka Adı</p>
<p className="font-medium text-sm">
{employee.bankAccount.bankName}
</p>
</div>
<div>
<p className="text-xs text-gray-600">Hesap Numarası</p>
<p className="font-mono text-xs">
{employee.bankAccount.accountNumber}
</p>
</div>
<div>
<p className="text-xs text-gray-600">IBAN</p>
<p className="font-mono text-xs">
{employee.bankAccount.iban}
</p>
</div>
</div>
</div>
)}
{/* İzin Durumu */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaCalendar className="w-4 h-4 text-sky-600 mr-2" />
İzin Durumu
</h3>
<div className="grid grid-cols-1 gap-2">
<div className="bg-white p-1.5 rounded-md flex justify-between items-center">
<span className="text-xs text-gray-600">Toplam İzin</span>
<span className="font-bold text-sm text-sky-600">
{employee.leaves?.length || 0}
</span>
</div>
<div className="bg-white p-1.5 rounded-md flex justify-between items-center">
<span className="text-xs text-gray-600">Kalan İzin</span>
<span className="font-bold text-sm text-emerald-600">
15 gün
</span>
</div>
</div>
</div>
{/* Sistem Bilgileri */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
<h3 className="text-sm font-semibold text-gray-900 mb-2 flex items-center">
<FaClock className="w-4 h-4 text-gray-600 mr-2" />
Sistem Bilgileri
</h3>
<div className="space-y-2 text-xs">
<div>
<p className="text-gray-600">Oluşturulma Tarihi</p>
<p className="font-medium text-gray-800">
{formatDate(employee.creationTime)}
</p>
</div>
<div>
<p className="text-gray-600">Son Güncelleme</p>
<p className="font-medium text-gray-800">
{formatDate(employee.lastModificationTime)}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="bg-gray-50 px-3 py-2 border-t flex-shrink-0">
<div className="flex justify-end">
<button
onClick={onClose}
className="px-3 py-1 text-sm bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors"
>
Kapat
</button>
</div>
</div>
</div>
</div>
);
};
export default EmployeeViewModal;

View file

@ -1,59 +1,55 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { FaPlus, FaEdit, FaEye, FaTh, FaList, FaSearch } from "react-icons/fa"; import { FaPlus, FaEdit, FaEye, FaTh, FaList, FaSearch } from 'react-icons/fa'
import { EmploymentTypeEnum } from "../../../types/hr"; import { EmploymentTypeEnum } from '../../../types/hr'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockEmployeeTypes } from "../../../mocks/mockEmployeeTypes"; import { mockEmployeeTypes } from '../../../mocks/mockEmployeeTypes'
import Widget from "../../../components/common/Widget"; import Widget from '../../../components/common/Widget'
import { import { getEmploymentTypeColor, getEmploymentTypeText } from '../../../utils/erp'
getEmploymentTypeColor, import { Container } from '@/components/shared'
getEmploymentTypeText,
} from "../../../utils/erp";
const EmploymentTypes: React.FC = () => { const EmploymentTypes: React.FC = () => {
const [viewMode, setViewMode] = useState<"card" | "list">("list"); const [viewMode, setViewMode] = useState<'card' | 'list'>('list')
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [selectedEmploymentType, setSelectedEmploymentType] = const [selectedEmploymentType, setSelectedEmploymentType] = useState<EmploymentTypeEnum | null>(
useState<EmploymentTypeEnum | null>(null); null,
const [showModal, setShowModal] = useState(false); )
const [modalMode, setModalMode] = useState<"new" | "edit" | "view">("view"); const [showModal, setShowModal] = useState(false)
const [modalMode, setModalMode] = useState<'new' | 'edit' | 'view'>('view')
const handleNewEmploymentType = () => { const handleNewEmploymentType = () => {
setModalMode("new"); setModalMode('new')
setSelectedEmploymentType(null); setSelectedEmploymentType(null)
setShowModal(true); setShowModal(true)
}; }
const handleEditEmploymentType = (type: EmploymentTypeEnum) => { const handleEditEmploymentType = (type: EmploymentTypeEnum) => {
setModalMode("edit"); setModalMode('edit')
setSelectedEmploymentType(type); setSelectedEmploymentType(type)
setShowModal(true); setShowModal(true)
}; }
const handleViewEmploymentType = (type: EmploymentTypeEnum) => { const handleViewEmploymentType = (type: EmploymentTypeEnum) => {
setModalMode("view"); setModalMode('view')
setSelectedEmploymentType(type); setSelectedEmploymentType(type)
setShowModal(true); setShowModal(true)
}; }
// Filter employee types based on search term // Filter employee types based on search term
const filteredEmployeeTypes = mockEmployeeTypes.filter((employeeType) => const filteredEmployeeTypes = mockEmployeeTypes.filter((employeeType) =>
getEmploymentTypeText(employeeType.name as EmploymentTypeEnum) getEmploymentTypeText(employeeType.name as EmploymentTypeEnum)
.toLowerCase() .toLowerCase()
.includes(searchTerm.toLowerCase()) .includes(searchTerm.toLowerCase()),
); )
return ( return (
<div className="space-y-3 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div> <div>
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">İstihdam Türleri</h2>
İstihdam Türleri <p className="text-gray-600 mt-1">Personel istihdam türleri ve dağılımı</p>
</h2>
<p className="text-sm text-gray-600 mt-1">
Personel istihdam türleri ve dağılımı
</p>
</div> </div>
{/* New Button - Visible on larger screens */} {/* New Button - Visible on larger screens */}
@ -87,22 +83,22 @@ const EmploymentTypes: React.FC = () => {
{/* View Toggle */} {/* View Toggle */}
<div className="flex bg-gray-100 rounded-lg p-1"> <div className="flex bg-gray-100 rounded-lg p-1">
<button <button
onClick={() => setViewMode("card")} onClick={() => setViewMode('card')}
className={`p-2 rounded ${ className={`p-2 rounded ${
viewMode === "card" viewMode === 'card'
? "bg-white shadow text-blue-600" ? 'bg-white shadow text-blue-600'
: "text-gray-600 hover:text-gray-800" : 'text-gray-600 hover:text-gray-800'
}`} }`}
title="Kart Görünümü" title="Kart Görünümü"
> >
<FaTh className="w-4 h-4" /> <FaTh className="w-4 h-4" />
</button> </button>
<button <button
onClick={() => setViewMode("list")} onClick={() => setViewMode('list')}
className={`p-2 rounded ${ className={`p-2 rounded ${
viewMode === "list" viewMode === 'list'
? "bg-white shadow text-blue-600" ? 'bg-white shadow text-blue-600'
: "text-gray-600 hover:text-gray-800" : 'text-gray-600 hover:text-gray-800'
}`} }`}
title="Liste Görünümü" title="Liste Görünümü"
> >
@ -136,9 +132,8 @@ const EmploymentTypes: React.FC = () => {
<Widget <Widget
title="Tam Zamanlı" title="Tam Zamanlı"
value={ value={
mockEmployees.filter( mockEmployees.filter((a) => a.employmentType === EmploymentTypeEnum.FullTime)
(a) => a.employmentType === EmploymentTypeEnum.FullTime .length || 0
).length || 0
} }
color="green" color="green"
icon="FaClock" icon="FaClock"
@ -147,9 +142,8 @@ const EmploymentTypes: React.FC = () => {
<Widget <Widget
title="Sözleşmeli" title="Sözleşmeli"
value={ value={
mockEmployees.filter( mockEmployees.filter((a) => a.employmentType === EmploymentTypeEnum.Contract)
(a) => a.employmentType === EmploymentTypeEnum.Contract .length || 0
).length || 0
} }
color="orange" color="orange"
icon="FaFileAlt" icon="FaFileAlt"
@ -165,14 +159,14 @@ const EmploymentTypes: React.FC = () => {
{/* Employment Type Distribution */} {/* Employment Type Distribution */}
<div className="bg-white rounded-lg shadow-sm border"> <div className="bg-white rounded-lg shadow-sm border">
{viewMode === "card" ? ( {viewMode === 'card' ? (
// Card View // Card View
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
{filteredEmployeeTypes.map((a) => { {filteredEmployeeTypes.map((a) => {
const type = a.name as EmploymentTypeEnum; // veya adan türet const type = a.name as EmploymentTypeEnum // veya adan türet
const count = a.count || 0; const count = a.count || 0
const total = mockEmployees.length; const total = mockEmployees.length
const percentage = total > 0 ? (count / total) * 100 : 0; const percentage = total > 0 ? (count / total) * 100 : 0
return ( return (
<div <div
@ -181,33 +175,23 @@ const EmploymentTypes: React.FC = () => {
> >
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div <div className={`w-4 h-4 rounded-full ${getEmploymentTypeColor(type)}`} />
className={`w-4 h-4 rounded-full ${getEmploymentTypeColor(
type
)}`}
/>
<h4 className="font-medium text-gray-900 text-sm"> <h4 className="font-medium text-gray-900 text-sm">
{getEmploymentTypeText(type)} {getEmploymentTypeText(type)}
</h4> </h4>
</div> </div>
<span className="text-xs text-gray-500"> <span className="text-xs text-gray-500">{percentage.toFixed(1)}%</span>
{percentage.toFixed(1)}%
</span>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-xs text-gray-600"> <span className="text-xs text-gray-600">Personel Sayısı</span>
Personel Sayısı
</span>
<span className="text-xs font-medium">{count}</span> <span className="text-xs font-medium">{count}</span>
</div> </div>
<div className="w-full bg-gray-200 rounded-full h-2"> <div className="w-full bg-gray-200 rounded-full h-2">
<div <div
className={`h-2 rounded-full ${getEmploymentTypeColor( className={`h-2 rounded-full ${getEmploymentTypeColor(type)}`}
type
)}`}
style={{ width: `${percentage}%` }} style={{ width: `${percentage}%` }}
/> />
</div> </div>
@ -233,7 +217,7 @@ const EmploymentTypes: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
); )
})} })}
</div> </div>
) : ( ) : (
@ -258,17 +242,17 @@ const EmploymentTypes: React.FC = () => {
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{filteredEmployeeTypes.map((a) => { {filteredEmployeeTypes.map((a) => {
const type = a.name as EmploymentTypeEnum; const type = a.name as EmploymentTypeEnum
const count = a.count || 0; const count = a.count || 0
const total = mockEmployees.length; const total = mockEmployees.length
const percentage = total > 0 ? (count / total) * 100 : 0; const percentage = total > 0 ? (count / total) * 100 : 0
return ( return (
<tr key={type} className="hover:bg-gray-50"> <tr key={type} className="hover:bg-gray-50">
<td className="px-3 py-2 whitespace-nowrap"> <td className="px-3 py-2 whitespace-nowrap">
<div className="flex items-center"> <div className="flex items-center">
<div <div
className={`w-4 h-4 rounded-full mr-3 ${getEmploymentTypeColor( className={`w-4 h-4 rounded-full mr-3 ${getEmploymentTypeColor(
type type,
)}`} )}`}
/> />
<div className="text-sm font-medium text-gray-900"> <div className="text-sm font-medium text-gray-900">
@ -283,15 +267,11 @@ const EmploymentTypes: React.FC = () => {
<div className="flex items-center"> <div className="flex items-center">
<div className="w-16 bg-gray-200 rounded-full h-2 mr-2"> <div className="w-16 bg-gray-200 rounded-full h-2 mr-2">
<div <div
className={`h-2 rounded-full ${getEmploymentTypeColor( className={`h-2 rounded-full ${getEmploymentTypeColor(type)}`}
type
)}`}
style={{ width: `${percentage}%` }} style={{ width: `${percentage}%` }}
/> />
</div> </div>
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">{percentage.toFixed(1)}%</span>
{percentage.toFixed(1)}%
</span>
</div> </div>
</td> </td>
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium"> <td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
@ -313,13 +293,14 @@ const EmploymentTypes: React.FC = () => {
</div> </div>
</td> </td>
</tr> </tr>
); )
})} })}
</tbody> </tbody>
</table> </table>
</div> </div>
)} )}
</div> </div>
</div>
{/* Modal */} {/* Modal */}
{showModal && ( {showModal && (
@ -327,9 +308,9 @@ const EmploymentTypes: React.FC = () => {
<div className="bg-white rounded-lg p-4 w-full max-w-md max-h-[90vh] overflow-y-auto"> <div className="bg-white rounded-lg p-4 w-full max-w-md max-h-[90vh] overflow-y-auto">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-base font-semibold"> <h3 className="text-base font-semibold">
{modalMode === "new" && "Yeni İstihdam Türü"} {modalMode === 'new' && 'Yeni İstihdam Türü'}
{modalMode === "edit" && "İstihdam Türünü Düzenle"} {modalMode === 'edit' && 'İstihdam Türünü Düzenle'}
{modalMode === "view" && "İstihdam Türü Detayları"} {modalMode === 'view' && 'İstihdam Türü Detayları'}
</h3> </h3>
<button <button
onClick={() => setShowModal(false)} onClick={() => setShowModal(false)}
@ -340,7 +321,7 @@ const EmploymentTypes: React.FC = () => {
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
{modalMode === "new" && ( {modalMode === 'new' && (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
@ -353,9 +334,7 @@ const EmploymentTypes: React.FC = () => {
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">ıklama</label>
ıklama
</label>
<textarea <textarea
rows={3} rows={3}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
@ -365,7 +344,7 @@ const EmploymentTypes: React.FC = () => {
</div> </div>
)} )}
{modalMode === "edit" && selectedEmploymentType && ( {modalMode === 'edit' && selectedEmploymentType && (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
@ -373,16 +352,12 @@ const EmploymentTypes: React.FC = () => {
</label> </label>
<input <input
type="text" type="text"
defaultValue={getEmploymentTypeText( defaultValue={getEmploymentTypeText(selectedEmploymentType)}
selectedEmploymentType
)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">ıklama</label>
ıklama
</label>
<textarea <textarea
rows={3} rows={3}
defaultValue="Bu istihdam türüne ait açıklama..." defaultValue="Bu istihdam türüne ait açıklama..."
@ -392,7 +367,7 @@ const EmploymentTypes: React.FC = () => {
</div> </div>
)} )}
{modalMode === "view" && selectedEmploymentType && ( {modalMode === 'view' && selectedEmploymentType && (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<p className="text-sm text-gray-600">İstihdam Türü:</p> <p className="text-sm text-gray-600">İstihdam Türü:</p>
@ -401,15 +376,12 @@ const EmploymentTypes: React.FC = () => {
</p> </p>
</div> </div>
<div> <div>
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">Toplam Personel Sayısı:</p>
Toplam Personel Sayısı:
</p>
<p className="font-medium"> <p className="font-medium">
{ {
mockEmployees.filter( mockEmployees.filter((emp) => emp.employmentType === selectedEmploymentType)
(emp) => emp.employmentType === selectedEmploymentType .length
).length }{' '}
}{" "}
kişi kişi
</p> </p>
</div> </div>
@ -418,7 +390,7 @@ const EmploymentTypes: React.FC = () => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className={`w-4 h-4 rounded-full ${getEmploymentTypeColor( className={`w-4 h-4 rounded-full ${getEmploymentTypeColor(
selectedEmploymentType selectedEmploymentType,
)}`} )}`}
/> />
<span className="text-sm"> <span className="text-sm">
@ -427,14 +399,10 @@ const EmploymentTypes: React.FC = () => {
</div> </div>
</div> </div>
<div> <div>
<p className="text-sm text-gray-600 mb-2"> <p className="text-sm text-gray-600 mb-2">Bu istihdam türündeki personeller:</p>
Bu istihdam türündeki personeller:
</p>
<div className="max-h-32 overflow-y-auto bg-gray-50 rounded p-2"> <div className="max-h-32 overflow-y-auto bg-gray-50 rounded p-2">
{mockEmployees {mockEmployees
.filter( .filter((emp) => emp.employmentType === selectedEmploymentType)
(emp) => emp.employmentType === selectedEmploymentType
)
.slice(0, 10) .slice(0, 10)
.map((emp) => ( .map((emp) => (
<div <div
@ -444,15 +412,13 @@ const EmploymentTypes: React.FC = () => {
{emp.fullName} - {emp.department?.name} {emp.fullName} - {emp.department?.name}
</div> </div>
))} ))}
{mockEmployees.filter( {mockEmployees.filter((emp) => emp.employmentType === selectedEmploymentType)
(emp) => emp.employmentType === selectedEmploymentType .length > 10 && (
).length > 10 && (
<div className="text-xs text-gray-500 pt-2"> <div className="text-xs text-gray-500 pt-2">
+ +
{mockEmployees.filter( {mockEmployees.filter(
(emp) => (emp) => emp.employmentType === selectedEmploymentType,
emp.employmentType === selectedEmploymentType ).length - 10}{' '}
).length - 10}{" "}
kişi daha... kişi daha...
</div> </div>
)} )}
@ -467,22 +433,22 @@ const EmploymentTypes: React.FC = () => {
onClick={() => setShowModal(false)} onClick={() => setShowModal(false)}
className="px-3 py-1.5 text-sm text-gray-600 border rounded-md hover:bg-gray-50 order-2 sm:order-1" className="px-3 py-1.5 text-sm text-gray-600 border rounded-md hover:bg-gray-50 order-2 sm:order-1"
> >
{modalMode === "view" ? "Kapat" : "İptal"} {modalMode === 'view' ? 'Kapat' : 'İptal'}
</button> </button>
{modalMode !== "view" && ( {modalMode !== 'view' && (
<button <button
onClick={() => setShowModal(false)} onClick={() => setShowModal(false)}
className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 order-1 sm:order-2" className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 order-1 sm:order-2"
> >
{modalMode === "new" ? "Oluştur" : "Güncelle"} {modalMode === 'new' ? 'Oluştur' : 'Güncelle'}
</button> </button>
)} )}
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </Container>
); )
}; }
export default EmploymentTypes; export default EmploymentTypes

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import {
FaBriefcase, FaBriefcase,
FaPlus, FaPlus,
@ -10,51 +10,50 @@ import {
FaEye, FaEye,
FaTh, FaTh,
FaList, FaList,
} from "react-icons/fa"; } from 'react-icons/fa'
import { HrJobPosition, JobLevelEnum } from "../../../types/hr"; import { HrJobPosition, JobLevelEnum } from '../../../types/hr'
import DataTable, { Column } from "../../../components/common/DataTable"; import DataTable, { Column } from '../../../components/common/DataTable'
import { mockJobPositions } from "../../../mocks/mockJobPositions"; import { mockJobPositions } from '../../../mocks/mockJobPositions'
import JobPositionFormModal from "./JobPositionFormModal"; import JobPositionFormModal from './JobPositionFormModal'
import JobPositionViewModal from "./JobPositionViewModal"; import JobPositionViewModal from './JobPositionViewModal'
import { getJobLevelColor, getJobLevelText } from "../../../utils/erp"; import { getJobLevelColor, getJobLevelText } from '../../../utils/erp'
import { Container } from '@/components/shared'
const JobPositions: React.FC = () => { const JobPositions: React.FC = () => {
const [positions, setPositions] = useState<HrJobPosition[]>(mockJobPositions); const [positions, setPositions] = useState<HrJobPosition[]>(mockJobPositions)
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [selectedLevel, setSelectedLevel] = useState<string>("all"); const [selectedLevel, setSelectedLevel] = useState<string>('all')
const [selectedDepartment, setSelectedDepartment] = useState<string>("all"); const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
const [viewMode, setViewMode] = useState<"list" | "card">("list"); const [viewMode, setViewMode] = useState<'list' | 'card'>('list')
// Modal states // Modal states
const [isFormModalOpen, setIsFormModalOpen] = useState(false); const [isFormModalOpen, setIsFormModalOpen] = useState(false)
const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false)
const [selectedPosition, setSelectedPosition] = useState< const [selectedPosition, setSelectedPosition] = useState<HrJobPosition | undefined>(undefined)
HrJobPosition | undefined const [modalTitle, setModalTitle] = useState('')
>(undefined);
const [modalTitle, setModalTitle] = useState("");
const handleAdd = () => { const handleAdd = () => {
setSelectedPosition(undefined); setSelectedPosition(undefined)
setModalTitle("Yeni İş Pozisyonu"); setModalTitle('Yeni İş Pozisyonu')
setIsFormModalOpen(true); setIsFormModalOpen(true)
}; }
const handleEdit = (position: HrJobPosition) => { const handleEdit = (position: HrJobPosition) => {
setSelectedPosition(position); setSelectedPosition(position)
setModalTitle("İş Pozisyonu Düzenle"); setModalTitle('İş Pozisyonu Düzenle')
setIsFormModalOpen(true); setIsFormModalOpen(true)
}; }
const handleView = (position: HrJobPosition) => { const handleView = (position: HrJobPosition) => {
setSelectedPosition(position); setSelectedPosition(position)
setIsViewModalOpen(true); setIsViewModalOpen(true)
}; }
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
if (window.confirm("Bu pozisyonu silmek istediğinizden emin misiniz?")) { if (window.confirm('Bu pozisyonu silmek istediğinizden emin misiniz?')) {
setPositions(positions.filter((p) => p.id !== id)); setPositions(positions.filter((p) => p.id !== id))
}
} }
};
const handleSavePosition = (positionData: Partial<HrJobPosition>) => { const handleSavePosition = (positionData: Partial<HrJobPosition>) => {
if (selectedPosition) { if (selectedPosition) {
@ -63,12 +62,8 @@ const JobPositions: React.FC = () => {
...selectedPosition, ...selectedPosition,
...positionData, ...positionData,
lastModificationTime: new Date(), lastModificationTime: new Date(),
}; }
setPositions( setPositions(positions.map((p) => (p.id === selectedPosition.id ? updatedPosition : p)))
positions.map((p) =>
p.id === selectedPosition.id ? updatedPosition : p
)
);
} else { } else {
// Add new position // Add new position
const newPosition: HrJobPosition = { const newPosition: HrJobPosition = {
@ -77,21 +72,21 @@ const JobPositions: React.FC = () => {
employees: [], employees: [],
creationTime: new Date(), creationTime: new Date(),
lastModificationTime: new Date(), lastModificationTime: new Date(),
} as HrJobPosition; } as HrJobPosition
setPositions([...positions, newPosition]); setPositions([...positions, newPosition])
}
} }
};
const closeFormModal = () => { const closeFormModal = () => {
setIsFormModalOpen(false); setIsFormModalOpen(false)
setSelectedPosition(undefined); setSelectedPosition(undefined)
setModalTitle(""); setModalTitle('')
}; }
const closeViewModal = () => { const closeViewModal = () => {
setIsViewModalOpen(false); setIsViewModalOpen(false)
setSelectedPosition(undefined); setSelectedPosition(undefined)
}; }
const filteredPositions = positions.filter((position) => { const filteredPositions = positions.filter((position) => {
if ( if (
@ -99,34 +94,27 @@ const JobPositions: React.FC = () => {
!position.name.toLowerCase().includes(searchTerm.toLowerCase()) && !position.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
!position.code.toLowerCase().includes(searchTerm.toLowerCase()) !position.code.toLowerCase().includes(searchTerm.toLowerCase())
) { ) {
return false; return false
} }
if (selectedLevel !== "all" && position.level !== selectedLevel) { if (selectedLevel !== 'all' && position.level !== selectedLevel) {
return false; return false
} }
if ( if (selectedDepartment !== 'all' && position.department?.id !== selectedDepartment) {
selectedDepartment !== "all" && return false
position.department?.id !== selectedDepartment
) {
return false;
} }
return true; return true
}); })
// Card component for individual position // Card component for individual position
const PositionCard: React.FC<{ position: HrJobPosition }> = ({ const PositionCard: React.FC<{ position: HrJobPosition }> = ({ position }) => (
position,
}) => (
<div className="bg-white rounded-lg shadow-sm border hover:shadow-md transition-shadow p-4"> <div className="bg-white rounded-lg shadow-sm border hover:shadow-md transition-shadow p-4">
<div className="flex items-start justify-between mb-4"> <div className="flex items-start justify-between mb-4">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<h3 className="text-base font-semibold text-gray-900"> <h3 className="text-base font-semibold text-gray-900">{position.name}</h3>
{position.name}
</h3>
<span <span
className={`px-2 py-1 text-xs font-medium rounded-full ${getJobLevelColor( className={`px-2 py-1 text-xs font-medium rounded-full ${getJobLevelColor(
position.level position.level,
)}`} )}`}
> >
{getJobLevelText(position.level)} {getJobLevelText(position.level)}
@ -136,9 +124,9 @@ const JobPositions: React.FC = () => {
<p <p
className="text-sm text-gray-700 overflow-hidden" className="text-sm text-gray-700 overflow-hidden"
style={{ style={{
display: "-webkit-box", display: '-webkit-box',
WebkitLineClamp: 2, WebkitLineClamp: 2,
WebkitBoxOrient: "vertical", WebkitBoxOrient: 'vertical',
}} }}
> >
{position.description} {position.description}
@ -146,19 +134,17 @@ const JobPositions: React.FC = () => {
</div> </div>
<span <span
className={`px-2 py-1 text-xs font-medium rounded-full ${ className={`px-2 py-1 text-xs font-medium rounded-full ${
position.isActive position.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`} }`}
> >
{position.isActive ? "Aktif" : "Pasif"} {position.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
</div> </div>
<div className="space-y-2 mb-3"> <div className="space-y-2 mb-3">
<div className="flex items-center gap-2 text-sm text-gray-600"> <div className="flex items-center gap-2 text-sm text-gray-600">
<FaBuilding className="w-4 h-4" /> <FaBuilding className="w-4 h-4" />
<span>{position.department?.name || "Departman belirtilmemiş"}</span> <span>{position.department?.name || 'Departman belirtilmemiş'}</span>
</div> </div>
<div className="flex items-center gap-2 text-sm text-gray-600"> <div className="flex items-center gap-2 text-sm text-gray-600">
@ -169,22 +155,16 @@ const JobPositions: React.FC = () => {
<div className="flex items-center gap-2 text-sm text-gray-600"> <div className="flex items-center gap-2 text-sm text-gray-600">
<FaDollarSign className="w-4 h-4" /> <FaDollarSign className="w-4 h-4" />
<span> <span>
{position.minSalary.toLocaleString()} - {position.minSalary.toLocaleString()} - {position.maxSalary.toLocaleString()}
{position.maxSalary.toLocaleString()}
</span> </span>
</div> </div>
</div> </div>
<div className="mb-3"> <div className="mb-3">
<p className="text-xs font-medium text-gray-500 mb-2"> <p className="text-xs font-medium text-gray-500 mb-2">Gerekli Yetenekler</p>
Gerekli Yetenekler
</p>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{position.requiredSkills?.slice(0, 4).map((skill, index) => ( {position.requiredSkills?.slice(0, 4).map((skill, index) => (
<span <span key={index} className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">
key={index}
className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded"
>
{skill} {skill}
</span> </span>
))} ))}
@ -219,44 +199,42 @@ const JobPositions: React.FC = () => {
</button> </button>
</div> </div>
</div> </div>
); )
const columns: Column<HrJobPosition>[] = [ const columns: Column<HrJobPosition>[] = [
{ {
key: "code", key: 'code',
header: "Pozisyon Kodu", header: 'Pozisyon Kodu',
sortable: true, sortable: true,
}, },
{ {
key: "title", key: 'title',
header: "Pozisyon Adı", header: 'Pozisyon Adı',
sortable: true, sortable: true,
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<div> <div>
<div className="font-medium text-gray-900">{position.name}</div> <div className="font-medium text-gray-900">{position.name}</div>
<div className="text-sm text-gray-500 truncate max-w-xs"> <div className="text-sm text-gray-500 truncate max-w-xs">{position.description}</div>
{position.description}
</div>
</div> </div>
), ),
}, },
{ {
key: "department", key: 'department',
header: "Departman", header: 'Departman',
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FaBuilding className="w-4 h-4 text-gray-500" /> <FaBuilding className="w-4 h-4 text-gray-500" />
<span>{position.department?.name || "-"}</span> <span>{position.department?.name || '-'}</span>
</div> </div>
), ),
}, },
{ {
key: "level", key: 'level',
header: "Seviye", header: 'Seviye',
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<span <span
className={`px-2 py-1 text-xs font-medium rounded-full ${getJobLevelColor( className={`px-2 py-1 text-xs font-medium rounded-full ${getJobLevelColor(
position.level position.level,
)}`} )}`}
> >
{getJobLevelText(position.level)} {getJobLevelText(position.level)}
@ -264,8 +242,8 @@ const JobPositions: React.FC = () => {
), ),
}, },
{ {
key: "employeeCount", key: 'employeeCount',
header: "Personel Sayısı", header: 'Personel Sayısı',
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaUsers className="w-4 h-4 text-gray-500" /> <FaUsers className="w-4 h-4 text-gray-500" />
@ -274,30 +252,25 @@ const JobPositions: React.FC = () => {
), ),
}, },
{ {
key: "salary", key: 'salary',
header: "Maaş Aralığı", header: 'Maaş Aralığı',
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FaDollarSign className="w-4 h-4 text-gray-500" /> <FaDollarSign className="w-4 h-4 text-gray-500" />
<div className="text-sm"> <div className="text-sm">
<div>{position.minSalary.toLocaleString()}</div> <div>{position.minSalary.toLocaleString()}</div>
<div className="text-gray-500"> <div className="text-gray-500">{position.maxSalary.toLocaleString()}</div>
{position.maxSalary.toLocaleString()}
</div>
</div> </div>
</div> </div>
), ),
}, },
{ {
key: "skills", key: 'skills',
header: "Gerekli Yetenekler", header: 'Gerekli Yetenekler',
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<div className="flex flex-wrap gap-1 max-w-xs"> <div className="flex flex-wrap gap-1 max-w-xs">
{position.requiredSkills?.slice(0, 3).map((skill, index) => ( {position.requiredSkills?.slice(0, 3).map((skill, index) => (
<span <span key={index} className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">
key={index}
className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded"
>
{skill} {skill}
</span> </span>
))} ))}
@ -310,23 +283,21 @@ const JobPositions: React.FC = () => {
), ),
}, },
{ {
key: "status", key: 'status',
header: "Durum", header: 'Durum',
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<span <span
className={`px-2 py-1 text-xs font-medium rounded-full ${ className={`px-2 py-1 text-xs font-medium rounded-full ${
position.isActive position.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`} }`}
> >
{position.isActive ? "Aktif" : "Pasif"} {position.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
), ),
}, },
{ {
key: "actions", key: 'actions',
header: "İşlemler", header: 'İşlemler',
render: (position: HrJobPosition) => ( render: (position: HrJobPosition) => (
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
@ -353,15 +324,14 @@ const JobPositions: React.FC = () => {
</div> </div>
), ),
}, },
]; ]
// Get unique departments for filter // Get unique departments for filter
const departments = [ const departments = [...new Set(positions.map((p) => p.department).filter(Boolean))]
...new Set(positions.map((p) => p.department).filter(Boolean)),
];
return ( return (
<div className="space-y-3 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div> <div>
@ -374,21 +344,21 @@ const JobPositions: React.FC = () => {
{/* View Toggle */} {/* View Toggle */}
<div className="flex bg-gray-100 rounded-lg p-0.5"> <div className="flex bg-gray-100 rounded-lg p-0.5">
<button <button
onClick={() => setViewMode("list")} onClick={() => setViewMode('list')}
className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${ className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${
viewMode === "list" viewMode === 'list'
? "bg-white text-gray-900 shadow-sm" ? 'bg-white text-gray-900 shadow-sm'
: "text-gray-500 hover:text-gray-700" : 'text-gray-500 hover:text-gray-700'
}`} }`}
> >
<FaList className="w-4 h-4" /> <FaList className="w-4 h-4" />
</button> </button>
<button <button
onClick={() => setViewMode("card")} onClick={() => setViewMode('card')}
className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${ className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${
viewMode === "card" viewMode === 'card'
? "bg-white text-gray-900 shadow-sm" ? 'bg-white text-gray-900 shadow-sm'
: "text-gray-500 hover:text-gray-700" : 'text-gray-500 hover:text-gray-700'
}`} }`}
> >
<FaTh className="w-4 h-4" /> <FaTh className="w-4 h-4" />
@ -412,12 +382,8 @@ const JobPositions: React.FC = () => {
<div className="flex items-center"> <div className="flex items-center">
<FaBriefcase className="w-6 h-6 text-blue-600" /> <FaBriefcase className="w-6 h-6 text-blue-600" />
<div className="ml-3"> <div className="ml-3">
<p className="text-xs font-medium text-gray-600"> <p className="text-xs font-medium text-gray-600">Toplam Pozisyon</p>
Toplam Pozisyon <p className="text-xl font-bold text-gray-900">{positions.length}</p>
</p>
<p className="text-xl font-bold text-gray-900">
{positions.length}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -426,14 +392,9 @@ const JobPositions: React.FC = () => {
<div className="flex items-center"> <div className="flex items-center">
<FaUsers className="w-6 h-6 text-green-600" /> <FaUsers className="w-6 h-6 text-green-600" />
<div className="ml-3"> <div className="ml-3">
<p className="text-xs font-medium text-gray-600"> <p className="text-xs font-medium text-gray-600">Dolu Pozisyonlar</p>
Dolu Pozisyonlar
</p>
<p className="text-xl font-bold text-gray-900"> <p className="text-xl font-bold text-gray-900">
{ {positions.filter((p) => p.employees && p.employees.length > 0).length}
positions.filter((p) => p.employees && p.employees.length > 0)
.length
}
</p> </p>
</div> </div>
</div> </div>
@ -443,15 +404,9 @@ const JobPositions: React.FC = () => {
<div className="flex items-center"> <div className="flex items-center">
<FaBriefcase className="w-6 h-6 text-orange-600" /> <FaBriefcase className="w-6 h-6 text-orange-600" />
<div className="ml-3"> <div className="ml-3">
<p className="text-xs font-medium text-gray-600"> <p className="text-xs font-medium text-gray-600">Boş Pozisyonlar</p>
Boş Pozisyonlar
</p>
<p className="text-xl font-bold text-gray-900"> <p className="text-xl font-bold text-gray-900">
{ {positions.filter((p) => !p.employees || p.employees.length === 0).length}
positions.filter(
(p) => !p.employees || p.employees.length === 0
).length
}
</p> </p>
</div> </div>
</div> </div>
@ -461,12 +416,8 @@ const JobPositions: React.FC = () => {
<div className="flex items-center"> <div className="flex items-center">
<FaBuilding className="w-6 h-6 text-purple-600" /> <FaBuilding className="w-6 h-6 text-purple-600" />
<div className="ml-3"> <div className="ml-3">
<p className="text-xs font-medium text-gray-600"> <p className="text-xs font-medium text-gray-600">Departman Sayısı</p>
Departman Sayısı <p className="text-xl font-bold text-gray-900">{departments.length}</p>
</p>
<p className="text-xl font-bold text-gray-900">
{departments.length}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -517,7 +468,7 @@ const JobPositions: React.FC = () => {
</div> </div>
{/* Content - List or Card View */} {/* Content - List or Card View */}
{viewMode === "list" ? ( {viewMode === 'list' ? (
<div className="bg-white rounded-lg shadow-sm border overflow-hidden"> <div className="bg-white rounded-lg shadow-sm border overflow-hidden">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<DataTable data={filteredPositions} columns={columns} /> <DataTable data={filteredPositions} columns={columns} />
@ -535,14 +486,11 @@ const JobPositions: React.FC = () => {
{filteredPositions.length === 0 && ( {filteredPositions.length === 0 && (
<div className="text-center py-12"> <div className="text-center py-12">
<FaBriefcase className="w-10 h-10 text-gray-400 mx-auto mb-3" /> <FaBriefcase className="w-10 h-10 text-gray-400 mx-auto mb-3" />
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">Pozisyon bulunamadı</h3>
Pozisyon bulunamadı <p className="text-sm text-gray-500">Arama kriterlerinizi değiştirmeyi deneyin.</p>
</h3>
<p className="text-sm text-gray-500">
Arama kriterlerinizi değiştirmeyi deneyin.
</p>
</div> </div>
)} )}
</div>
{/* Modals */} {/* Modals */}
<JobPositionFormModal <JobPositionFormModal
@ -558,8 +506,8 @@ const JobPositions: React.FC = () => {
onClose={closeViewModal} onClose={closeViewModal}
position={selectedPosition} position={selectedPosition}
/> />
</div> </Container>
); )
}; }
export default JobPositions; export default JobPositions

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from "react"; import React, { useState, useEffect, useMemo } from 'react'
import { import {
FaUser, FaUser,
FaUsers, FaUsers,
@ -14,54 +14,49 @@ import {
FaPhone, FaPhone,
FaMapMarkerAlt, FaMapMarkerAlt,
FaBriefcase, FaBriefcase,
} from "react-icons/fa"; } from 'react-icons/fa'
import { HrEmployee, HrOrganizationChart as OrgChart } from "../../../types/hr"; import { HrEmployee, HrOrganizationChart as OrgChart } from '../../../types/hr'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockDepartments } from "../../../mocks/mockDepartments"; import { mockDepartments } from '../../../mocks/mockDepartments'
import Widget from "../../../components/common/Widget"; import Widget from '../../../components/common/Widget'
import { Container } from '@/components/shared'
// Dinamik organizasyon verisi oluşturma fonksiyonu // Dinamik organizasyon verisi oluşturma fonksiyonu
const generateOrganizationData = (): OrgChart[] => { const generateOrganizationData = (): OrgChart[] => {
const orgData: OrgChart[] = []; const orgData: OrgChart[] = []
// Çalışanları işle // Çalışanları işle
mockEmployees.forEach((employee) => { mockEmployees.forEach((employee) => {
const department = mockDepartments.find( const department = mockDepartments.find((d) => d.id === employee.departmantId)
(d) => d.id === employee.departmantId
);
let level = 3; // Varsayılan seviye (normal çalışan) let level = 3 // Varsayılan seviye (normal çalışan)
let parentId: string | undefined = undefined; let parentId: string | undefined = undefined
// Eğer bu çalışan bir departman yöneticisiyse // Eğer bu çalışan bir departman yöneticisiyse
if (department && department.managerId === employee.id) { if (department && department.managerId === employee.id) {
if (!department.parentDepartmentId) { if (!department.parentDepartmentId) {
// Ana departman yöneticisi (CEO/Genel Müdür) // Ana departman yöneticisi (CEO/Genel Müdür)
level = 0; level = 0
} else { } else {
// Alt departman yöneticisi // Alt departman yöneticisi
level = 1; level = 1
// Üst departmanın yöneticisini parent olarak bul // Üst departmanın yöneticisini parent olarak bul
const parentDept = mockDepartments.find( const parentDept = mockDepartments.find((d) => d.id === department.parentDepartmentId)
(d) => d.id === department.parentDepartmentId
);
if (parentDept && parentDept.managerId) { if (parentDept && parentDept.managerId) {
parentId = parentDept.managerId; parentId = parentDept.managerId
} }
} }
} else { } else {
// Normal çalışan - departman yöneticisine bağlı // Normal çalışan - departman yöneticisine bağlı
if (department && department.managerId) { if (department && department.managerId) {
parentId = department.managerId; parentId = department.managerId
// Departman yöneticisinin seviyesine göre belirleme // Departman yöneticisinin seviyesine göre belirleme
const managerDept = mockDepartments.find( const managerDept = mockDepartments.find((d) => d.managerId === department.managerId)
(d) => d.managerId === department.managerId
);
if (managerDept && !managerDept.parentDepartmentId) { if (managerDept && !managerDept.parentDepartmentId) {
level = 2; // CEO'ya direkt bağlı level = 2 // CEO'ya direkt bağlı
} else { } else {
level = 3; // Orta kademe yöneticiye bağlı level = 3 // Orta kademe yöneticiye bağlı
} }
} }
} }
@ -72,46 +67,44 @@ const generateOrganizationData = (): OrgChart[] => {
employee: employee, employee: employee,
parentId: parentId, parentId: parentId,
level: level, level: level,
position: employee.jobPosition?.name || "Belirsiz", position: employee.jobPosition?.name || 'Belirsiz',
isActive: employee.isActive, isActive: employee.isActive,
}); })
}); })
return orgData.sort((a, b) => a.level - b.level); return orgData.sort((a, b) => a.level - b.level)
}; }
interface TreeNode { interface TreeNode {
employee: HrEmployee; employee: HrEmployee
children: TreeNode[]; children: TreeNode[]
level: number; level: number
} }
const OrganizationChart: React.FC = () => { const OrganizationChart: React.FC = () => {
const [employees] = useState<HrEmployee[]>(mockEmployees); const [employees] = useState<HrEmployee[]>(mockEmployees)
const [organizationData] = useState<OrgChart[]>(generateOrganizationData()); const [organizationData] = useState<OrgChart[]>(generateOrganizationData())
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set()); const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set())
const [orgTree, setOrgTree] = useState<TreeNode[]>([]); const [orgTree, setOrgTree] = useState<TreeNode[]>([])
const [viewMode, setViewMode] = useState<"tree" | "cards">("tree"); const [viewMode, setViewMode] = useState<'tree' | 'cards'>('tree')
const [selectedEmployee, setSelectedEmployee] = useState<HrEmployee | null>( const [selectedEmployee, setSelectedEmployee] = useState<HrEmployee | null>(null)
null const [showModal, setShowModal] = useState(false)
); const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
const [showModal, setShowModal] = useState(false);
const [selectedDepartment, setSelectedDepartment] = useState<string>("all");
const handleViewEmployee = (employee: HrEmployee) => { const handleViewEmployee = (employee: HrEmployee) => {
setSelectedEmployee(employee); setSelectedEmployee(employee)
setShowModal(true); setShowModal(true)
}; }
const handleCloseModal = () => { const handleCloseModal = () => {
setShowModal(false); setShowModal(false)
setSelectedEmployee(null); setSelectedEmployee(null)
}; }
useEffect(() => { useEffect(() => {
// Build organization tree structure // Build organization tree structure
const buildTree = (): TreeNode[] => { const buildTree = (): TreeNode[] => {
const nodeMap = new Map<string, TreeNode>(); const nodeMap = new Map<string, TreeNode>()
// First, create all nodes // First, create all nodes
employees.forEach((employee) => { employees.forEach((employee) => {
@ -119,82 +112,79 @@ const OrganizationChart: React.FC = () => {
employee, employee,
children: [], children: [],
level: 0, level: 0,
}); })
}); })
// Then, build the tree structure // Then, build the tree structure
const rootNodes: TreeNode[] = []; const rootNodes: TreeNode[] = []
organizationData.forEach((orgData) => { organizationData.forEach((orgData) => {
const node = nodeMap.get(orgData.employeeId); const node = nodeMap.get(orgData.employeeId)
if (node) { if (node) {
node.level = orgData.level; node.level = orgData.level
if (orgData.parentId) { if (orgData.parentId) {
const parentNode = nodeMap.get(orgData.parentId); const parentNode = nodeMap.get(orgData.parentId)
if (parentNode) { if (parentNode) {
parentNode.children.push(node); parentNode.children.push(node)
} }
} else { } else {
rootNodes.push(node); rootNodes.push(node)
} }
} }
}); })
return rootNodes; return rootNodes
}; }
setOrgTree(buildTree()); setOrgTree(buildTree())
// Auto-expand first level // Auto-expand first level
const firstLevelIds = organizationData const firstLevelIds = organizationData
.filter((org) => org.level <= 1) .filter((org) => org.level <= 1)
.map((org) => org.employeeId); .map((org) => org.employeeId)
setExpandedNodes(new Set(firstLevelIds)); setExpandedNodes(new Set(firstLevelIds))
}, [employees, organizationData]); }, [employees, organizationData])
// Filtered data for views // Filtered data for views
const filteredOrgTree = useMemo(() => { const filteredOrgTree = useMemo(() => {
if (selectedDepartment === "all") { if (selectedDepartment === 'all') {
return orgTree; return orgTree
} }
const filterTree = (nodes: TreeNode[]): TreeNode[] => { const filterTree = (nodes: TreeNode[]): TreeNode[] => {
const result: TreeNode[] = []; const result: TreeNode[] = []
for (const node of nodes) { for (const node of nodes) {
const filteredChildren = filterTree(node.children); const filteredChildren = filterTree(node.children)
if ( if (node.employee.departmantId === selectedDepartment || filteredChildren.length > 0) {
node.employee.departmantId === selectedDepartment || result.push({ ...node, children: filteredChildren })
filteredChildren.length > 0
) {
result.push({ ...node, children: filteredChildren });
} }
} }
return result; return result
}; }
return filterTree(orgTree); return filterTree(orgTree)
}, [orgTree, selectedDepartment]); }, [orgTree, selectedDepartment])
const filteredEmployees = useMemo(() => { const filteredEmployees = useMemo(() => {
if (selectedDepartment === "all") { if (selectedDepartment === 'all') {
return employees; return employees
} }
return employees.filter((emp) => emp.departmantId === selectedDepartment); return employees.filter((emp) => emp.departmantId === selectedDepartment)
}, [employees, selectedDepartment]); }, [employees, selectedDepartment])
const toggleNode = (nodeId: string) => { const toggleNode = (nodeId: string) => {
const newExpanded = new Set(expandedNodes); const newExpanded = new Set(expandedNodes)
if (newExpanded.has(nodeId)) { if (newExpanded.has(nodeId)) {
newExpanded.delete(nodeId); newExpanded.delete(nodeId)
} else { } else {
newExpanded.add(nodeId); newExpanded.add(nodeId)
}
setExpandedNodes(newExpanded)
} }
setExpandedNodes(newExpanded);
};
const renderTreeNode = (node: TreeNode): React.ReactNode => { const renderTreeNode = (node: TreeNode): React.ReactNode => {
const isExpanded = expandedNodes.has(node.employee.id); const isExpanded = expandedNodes.has(node.employee.id)
const hasChildren = node.children.length > 0; const hasChildren = node.children.length > 0
return ( return (
<div key={node.employee.id} className="relative"> <div key={node.employee.id} className="relative">
@ -205,7 +195,7 @@ const OrganizationChart: React.FC = () => {
style={{ style={{
left: `${node.level * 1.25 - 0.7}rem`, left: `${node.level * 1.25 - 0.7}rem`,
top: 0, top: 0,
height: "100%", height: '100%',
}} }}
/> />
)} )}
@ -217,7 +207,7 @@ const OrganizationChart: React.FC = () => {
{node.level > 0 && ( {node.level > 0 && (
<span <span
className="absolute border-t border-gray-300 w-3" className="absolute border-t border-gray-300 w-3"
style={{ left: `${node.level * 1.25 - 0.7}rem`, top: "1.1rem" }} style={{ left: `${node.level * 1.25 - 0.7}rem`, top: '1.1rem' }}
/> />
)} )}
@ -228,11 +218,7 @@ const OrganizationChart: React.FC = () => {
onClick={() => toggleNode(node.employee.id)} onClick={() => toggleNode(node.employee.id)}
className="p-1 text-gray-500 hover:text-gray-800 rounded-full hover:bg-gray-200" className="p-1 text-gray-500 hover:text-gray-800 rounded-full hover:bg-gray-200"
> >
{isExpanded ? ( {isExpanded ? <FaChevronDown size={10} /> : <FaChevronRight size={10} />}
<FaChevronDown size={10} />
) : (
<FaChevronRight size={10} />
)}
</button> </button>
)} )}
</div> </div>
@ -248,9 +234,7 @@ const OrganizationChart: React.FC = () => {
<p className="text-xs text-gray-600 truncate"> <p className="text-xs text-gray-600 truncate">
{node.employee.jobPosition?.name} {node.employee.jobPosition?.name}
</p> </p>
<p className="text-xs text-gray-500 truncate"> <p className="text-xs text-gray-500 truncate">{node.employee.department?.name}</p>
{node.employee.department?.name}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -279,22 +263,23 @@ const OrganizationChart: React.FC = () => {
<div>{node.children.map((child) => renderTreeNode(child))}</div> <div>{node.children.map((child) => renderTreeNode(child))}</div>
)} )}
</div> </div>
); )
}; }
const renderCardView = () => { const renderCardView = () => {
const groupedByLevel = filteredEmployees.reduce((acc, employee) => { const groupedByLevel = filteredEmployees.reduce(
const orgData = organizationData.find( (acc, employee) => {
(org) => org.employeeId === employee.id const orgData = organizationData.find((org) => org.employeeId === employee.id)
); const level = orgData?.level || 0
const level = orgData?.level || 0;
if (!acc[level]) { if (!acc[level]) {
acc[level] = []; acc[level] = []
} }
acc[level].push(employee); acc[level].push(employee)
return acc; return acc
}, {} as Record<number, HrEmployee[]>); },
{} as Record<number, HrEmployee[]>,
)
return ( return (
<div className="space-y-6"> <div className="space-y-6">
@ -304,12 +289,12 @@ const OrganizationChart: React.FC = () => {
<div key={level}> <div key={level}>
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2"> <h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<FaBuilding className="w-5 h-5" /> <FaBuilding className="w-5 h-5" />
{level === "0" {level === '0'
? "Üst Yönetim" ? 'Üst Yönetim'
: level === "1" : level === '1'
? "Orta Kademe Yönetim" ? 'Orta Kademe Yönetim'
: level === "2" : level === '2'
? "Alt Kademe Yönetim" ? 'Alt Kademe Yönetim'
: `${parseInt(level) + 1}. Seviye`} : `${parseInt(level) + 1}. Seviye`}
<span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded-full"> <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
{levelEmployees.length} kişi {levelEmployees.length} kişi
@ -325,20 +310,20 @@ const OrganizationChart: React.FC = () => {
<div className="flex items-center mb-3"> <div className="flex items-center mb-3">
<div <div
className={`w-8 h-8 rounded-full flex items-center justify-center mr-2 ${ className={`w-8 h-8 rounded-full flex items-center justify-center mr-2 ${
level === "0" level === '0'
? "bg-blue-100" ? 'bg-blue-100'
: level === "1" : level === '1'
? "bg-green-100" ? 'bg-green-100'
: "bg-gray-100" : 'bg-gray-100'
}`} }`}
> >
<FaUser <FaUser
className={`w-4 h-4 ${ className={`w-4 h-4 ${
level === "0" level === '0'
? "text-blue-600" ? 'text-blue-600'
: level === "1" : level === '1'
? "text-green-600" ? 'text-green-600'
: "text-gray-600" : 'text-gray-600'
}`} }`}
/> />
</div> </div>
@ -350,9 +335,7 @@ const OrganizationChart: React.FC = () => {
> >
{employee.fullName} {employee.fullName}
</h4> </h4>
<p className="text-sm text-gray-600 truncate"> <p className="text-sm text-gray-600 truncate">{employee.code}</p>
{employee.code}
</p>
</div> </div>
</div> </div>
@ -360,9 +343,7 @@ const OrganizationChart: React.FC = () => {
<p className="text-xs text-gray-800 font-medium truncate"> <p className="text-xs text-gray-800 font-medium truncate">
{employee.jobPosition?.name} {employee.jobPosition?.name}
</p> </p>
<p className="text-xs text-gray-600 truncate"> <p className="text-xs text-gray-600 truncate">{employee.department?.name}</p>
{employee.department?.name}
</p>
</div> </div>
</div> </div>
))} ))}
@ -370,20 +351,17 @@ const OrganizationChart: React.FC = () => {
</div> </div>
))} ))}
</div> </div>
); )
}; }
return ( return (
<div className="space-y-3 pt-2"> <Container>
<div className="space-y-2">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">Organizasyon Şeması</h2>
Organizasyon Şeması <p className="text-sm text-gray-600 mt-1">Kurumsal hiyerarşi ve raporlama yapısı</p>
</h2>
<p className="text-sm text-gray-600 mt-1">
Kurumsal hiyerarşi ve raporlama yapısı
</p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -401,21 +379,21 @@ const OrganizationChart: React.FC = () => {
</select> </select>
<div className="flex bg-gray-100 rounded-lg p-0.5"> <div className="flex bg-gray-100 rounded-lg p-0.5">
<button <button
onClick={() => setViewMode("tree")} onClick={() => setViewMode('tree')}
className={`p-1.5 rounded-md transition-colors ${ className={`p-1.5 rounded-md transition-colors ${
viewMode === "tree" viewMode === 'tree'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
> >
<FaSitemap className="w-4 h-4" /> <FaSitemap className="w-4 h-4" />
</button> </button>
<button <button
onClick={() => setViewMode("cards")} onClick={() => setViewMode('cards')}
className={`p-1.5 rounded-md transition-colors ${ className={`p-1.5 rounded-md transition-colors ${
viewMode === "cards" viewMode === 'cards'
? "bg-white text-blue-600 shadow-sm" ? 'bg-white text-blue-600 shadow-sm'
: "text-gray-600 hover:text-gray-900" : 'text-gray-600 hover:text-gray-900'
}`} }`}
> >
<FaTh className="w-4 h-4" /> <FaTh className="w-4 h-4" />
@ -426,18 +404,11 @@ const OrganizationChart: React.FC = () => {
{/* Stats */} {/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3"> <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<Widget <Widget title="Toplam Personel" value={employees.length} color="blue" icon="FaBuilding" />
title="Toplam Personel"
value={employees.length}
color="blue"
icon="FaBuilding"
/>
<Widget <Widget
title="Departmanlar" title="Departmanlar"
value={ value={new Set(employees.map((e) => e.department?.id).filter(Boolean)).size}
new Set(employees.map((e) => e.department?.id).filter(Boolean)).size
}
color="purple" color="purple"
icon="FaBuilding" icon="FaBuilding"
/> />
@ -456,15 +427,13 @@ const OrganizationChart: React.FC = () => {
{/* Content */} {/* Content */}
<div className="bg-white rounded-lg border p-4"> <div className="bg-white rounded-lg border p-4">
{viewMode === "tree" ? ( {viewMode === 'tree' ? (
<div className="pt-2"> <div className="pt-2">{filteredOrgTree.map((node) => renderTreeNode(node))}</div>
{filteredOrgTree.map((node) => renderTreeNode(node))}
</div>
) : ( ) : (
renderCardView() renderCardView()
)} )}
{(viewMode === "tree" {(viewMode === 'tree'
? filteredOrgTree.length === 0 ? filteredOrgTree.length === 0
: filteredEmployees.length === 0) && ( : filteredEmployees.length === 0) && (
<div className="text-center py-12"> <div className="text-center py-12">
@ -472,12 +441,11 @@ const OrganizationChart: React.FC = () => {
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">
Organizasyon verisi bulunamadı Organizasyon verisi bulunamadı
</h3> </h3>
<p className="text-gray-500"> <p className="text-gray-500">Henüz organizasyon şeması oluşturulmamış.</p>
Henüz organizasyon şeması oluşturulmamış.
</p>
</div> </div>
)} )}
</div> </div>
</div>
{/* Employee Detail Modal */} {/* Employee Detail Modal */}
{showModal && selectedEmployee && ( {showModal && selectedEmployee && (
@ -485,13 +453,8 @@ const OrganizationChart: React.FC = () => {
<div className="bg-white rounded-lg p-4 w-full max-w-2xl max-h-[90vh] overflow-y-auto"> <div className="bg-white rounded-lg p-4 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
{/* Modal Header */} {/* Modal Header */}
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold text-gray-900"> <h3 className="text-lg font-semibold text-gray-900">Personel Detayları</h3>
Personel Detayları <button onClick={handleCloseModal} className="text-gray-400 hover:text-gray-600 p-1">
</h3>
<button
onClick={handleCloseModal}
className="text-gray-400 hover:text-gray-600 p-1"
>
<FaTimes className="w-5 h-5" /> <FaTimes className="w-5 h-5" />
</button> </button>
</div> </div>
@ -504,13 +467,9 @@ const OrganizationChart: React.FC = () => {
<FaUser className="w-8 h-8 text-blue-600" /> <FaUser className="w-8 h-8 text-blue-600" />
</div> </div>
<div> <div>
<h4 className="text-lg font-bold text-gray-900"> <h4 className="text-lg font-bold text-gray-900">{selectedEmployee.fullName}</h4>
{selectedEmployee.fullName}
</h4>
<p className="text-gray-600">{selectedEmployee.code}</p> <p className="text-gray-600">{selectedEmployee.code}</p>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">{selectedEmployee.jobPosition?.name}</p>
{selectedEmployee.jobPosition?.name}
</p>
</div> </div>
</div> </div>
@ -524,17 +483,13 @@ const OrganizationChart: React.FC = () => {
<div className="flex items-center"> <div className="flex items-center">
<FaEnvelope className="w-4 h-4 text-gray-500 mr-3" /> <FaEnvelope className="w-4 h-4 text-gray-500 mr-3" />
<span className="text-sm">E-posta:</span> <span className="text-sm">E-posta:</span>
<span className="ml-2 text-sm font-medium"> <span className="ml-2 text-sm font-medium">{selectedEmployee.email}</span>
{selectedEmployee.email}
</span>
</div> </div>
{selectedEmployee.phone && ( {selectedEmployee.phone && (
<div className="flex items-center"> <div className="flex items-center">
<FaPhone className="w-4 h-4 text-gray-500 mr-3" /> <FaPhone className="w-4 h-4 text-gray-500 mr-3" />
<span className="text-sm">İş Telefonu:</span> <span className="text-sm">İş Telefonu:</span>
<span className="ml-2 text-sm font-medium"> <span className="ml-2 text-sm font-medium">{selectedEmployee.phone}</span>
{selectedEmployee.phone}
</span>
</div> </div>
)} )}
{selectedEmployee.personalPhone && ( {selectedEmployee.personalPhone && (
@ -559,39 +514,25 @@ const OrganizationChart: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<span className="text-sm text-gray-600">Departman:</span> <span className="text-sm text-gray-600">Departman:</span>
<p className="font-medium"> <p className="font-medium">{selectedEmployee.department?.name}</p>
{selectedEmployee.department?.name}
</p>
</div> </div>
<div> <div>
<span className="text-sm text-gray-600">Pozisyon:</span> <span className="text-sm text-gray-600">Pozisyon:</span>
<p className="font-medium"> <p className="font-medium">{selectedEmployee.jobPosition?.name}</p>
{selectedEmployee.jobPosition?.name}
</p>
</div> </div>
<div> <div>
<span className="text-sm text-gray-600"> <span className="text-sm text-gray-600">İşe Başlama Tarihi:</span>
İşe Başlama Tarihi:
</span>
<p className="font-medium flex items-center"> <p className="font-medium flex items-center">
<FaCalendar className="w-4 h-4 mr-1 text-gray-500" /> <FaCalendar className="w-4 h-4 mr-1 text-gray-500" />
{new Date(selectedEmployee.hireDate).toLocaleDateString( {new Date(selectedEmployee.hireDate).toLocaleDateString('tr-TR')}
"tr-TR"
)}
</p> </p>
</div> </div>
<div> <div>
<span className="text-sm text-gray-600"> <span className="text-sm text-gray-600">İstihdam Türü:</span>
İstihdam Türü: <p className="font-medium">{selectedEmployee.employmentType}</p>
</span>
<p className="font-medium">
{selectedEmployee.employmentType}
</p>
</div> </div>
<div> <div>
<span className="text-sm text-gray-600"> <span className="text-sm text-gray-600">Çalışma Lokasyonu:</span>
Çalışma Lokasyonu:
</span>
<p className="font-medium flex items-center"> <p className="font-medium flex items-center">
<FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500" /> <FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500" />
{selectedEmployee.workLocation} {selectedEmployee.workLocation}
@ -602,11 +543,11 @@ const OrganizationChart: React.FC = () => {
<span <span
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${ className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
selectedEmployee.isActive selectedEmployee.isActive
? "bg-green-100 text-green-800" ? 'bg-green-100 text-green-800'
: "bg-red-100 text-red-800" : 'bg-red-100 text-red-800'
}`} }`}
> >
{selectedEmployee.isActive ? "Aktif" : "Pasif"} {selectedEmployee.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
</div> </div>
</div> </div>
@ -622,14 +563,10 @@ const OrganizationChart: React.FC = () => {
<div className="bg-gray-50 p-3 rounded-lg space-y-2"> <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 className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<span className="text-sm text-gray-600"> <span className="text-sm text-gray-600">Doğum Tarihi:</span>
Doğum Tarihi:
</span>
<p className="font-medium flex items-center"> <p className="font-medium flex items-center">
<FaCalendar className="w-4 h-4 mr-1 text-gray-500" /> <FaCalendar className="w-4 h-4 mr-1 text-gray-500" />
{new Date( {new Date(selectedEmployee.birthDate).toLocaleDateString('tr-TR')}
selectedEmployee.birthDate
).toLocaleDateString("tr-TR")}
</p> </p>
</div> </div>
<div> <div>
@ -637,20 +574,12 @@ const OrganizationChart: React.FC = () => {
<p className="font-medium">{selectedEmployee.gender}</p> <p className="font-medium">{selectedEmployee.gender}</p>
</div> </div>
<div> <div>
<span className="text-sm text-gray-600"> <span className="text-sm text-gray-600">Medeni Durum:</span>
Medeni Durum: <p className="font-medium">{selectedEmployee.maritalStatus}</p>
</span>
<p className="font-medium">
{selectedEmployee.maritalStatus}
</p>
</div> </div>
<div> <div>
<span className="text-sm text-gray-600"> <span className="text-sm text-gray-600">TC Kimlik No:</span>
TC Kimlik No: <p className="font-medium">{selectedEmployee.nationalId}</p>
</span>
<p className="font-medium">
{selectedEmployee.nationalId}
</p>
</div> </div>
</div> </div>
@ -659,8 +588,7 @@ const OrganizationChart: React.FC = () => {
<span className="text-sm text-gray-600">Adres:</span> <span className="text-sm text-gray-600">Adres:</span>
<p className="font-medium flex items-start"> <p className="font-medium flex items-start">
<FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500 mt-0.5" /> <FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500 mt-0.5" />
{selectedEmployee.address.street},{" "} {selectedEmployee.address.street}, {selectedEmployee.address.city},{' '}
{selectedEmployee.address.city},{" "}
{selectedEmployee.address.country} {selectedEmployee.address.country}
</p> </p>
</div> </div>
@ -679,9 +607,7 @@ const OrganizationChart: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<span className="text-sm text-gray-600">İsim:</span> <span className="text-sm text-gray-600">İsim:</span>
<p className="font-medium"> <p className="font-medium">{selectedEmployee.emergencyContact.name}</p>
{selectedEmployee.emergencyContact.name}
</p>
</div> </div>
<div> <div>
<span className="text-sm text-gray-600">Yakınlık:</span> <span className="text-sm text-gray-600">Yakınlık:</span>
@ -691,9 +617,7 @@ const OrganizationChart: React.FC = () => {
</div> </div>
<div> <div>
<span className="text-sm text-gray-600">Telefon:</span> <span className="text-sm text-gray-600">Telefon:</span>
<p className="font-medium"> <p className="font-medium">{selectedEmployee.emergencyContact.phone}</p>
{selectedEmployee.emergencyContact.phone}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -713,8 +637,8 @@ const OrganizationChart: React.FC = () => {
</div> </div>
</div> </div>
)} )}
</div> </Container>
); )
}; }
export default OrganizationChart; export default OrganizationChart

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff