IK Management Container
This commit is contained in:
parent
4e5322ba0c
commit
0cb87b8295
16 changed files with 5683 additions and 7071 deletions
|
|
@ -1,64 +1,55 @@
|
|||
import React, { useState } from "react";
|
||||
import { FaAward, FaPlus, FaEdit, FaTrash, FaUsers } from "react-icons/fa";
|
||||
import { HrBadge, HrEmployeeBadge } from "../../../types/hr";
|
||||
import DataTable, { Column } from "../../../components/common/DataTable";
|
||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||||
import { mockBadges } from "../../../mocks/mockBadges";
|
||||
import BadgeEditModal from "./BadgeEditModal";
|
||||
import BadgeAssignmentModal, {
|
||||
BadgeAssignmentFormData,
|
||||
} from "./BadgeAssignmentModal";
|
||||
import Widget from "../../../components/common/Widget";
|
||||
import { getIconComponent } from "../../../utils/erp";
|
||||
import React, { useState } from 'react'
|
||||
import { FaAward, FaPlus, FaEdit, FaTrash, FaUsers } from 'react-icons/fa'
|
||||
import { HrBadge, HrEmployeeBadge } from '../../../types/hr'
|
||||
import DataTable, { Column } from '../../../components/common/DataTable'
|
||||
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||
import { mockBadges } from '../../../mocks/mockBadges'
|
||||
import BadgeEditModal from './BadgeEditModal'
|
||||
import BadgeAssignmentModal, { BadgeAssignmentFormData } from './BadgeAssignmentModal'
|
||||
import Widget from '../../../components/common/Widget'
|
||||
import { getIconComponent } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const BadgeManagement: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<"badges" | "assignments">(
|
||||
"badges"
|
||||
);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [searchAssignTerm, setSearchAssignTerm] = useState("");
|
||||
const [activeTab, setActiveTab] = useState<'badges' | 'assignments'>('badges')
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [searchAssignTerm, setSearchAssignTerm] = useState('')
|
||||
|
||||
// Modal states
|
||||
const [isBadgeModalOpen, setIsBadgeModalOpen] = useState(false);
|
||||
const [isAssignmentModalOpen, setIsAssignmentModalOpen] = useState(false);
|
||||
const [editingBadge, setEditingBadge] = useState<HrBadge | null>(null);
|
||||
const [isBadgeModalOpen, setIsBadgeModalOpen] = useState(false)
|
||||
const [isAssignmentModalOpen, setIsAssignmentModalOpen] = useState(false)
|
||||
const [editingBadge, setEditingBadge] = useState<HrBadge | null>(null)
|
||||
|
||||
// Mock data - In real app, this would come from API
|
||||
const [badges, setBadges] = useState(mockBadges);
|
||||
const [employeeBadges, setEmployeeBadges] = useState<HrEmployeeBadge[]>([]);
|
||||
const [badges, setBadges] = useState(mockBadges)
|
||||
const [employeeBadges, setEmployeeBadges] = useState<HrEmployeeBadge[]>([])
|
||||
|
||||
// Handlers
|
||||
const handleAddBadge = () => {
|
||||
setEditingBadge(null);
|
||||
setIsBadgeModalOpen(true);
|
||||
};
|
||||
setEditingBadge(null)
|
||||
setIsBadgeModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEditBadge = (badge: HrBadge) => {
|
||||
setEditingBadge(badge);
|
||||
setIsBadgeModalOpen(true);
|
||||
};
|
||||
setEditingBadge(badge)
|
||||
setIsBadgeModalOpen(true)
|
||||
}
|
||||
|
||||
const handleDeleteBadge = (id: string) => {
|
||||
if (window.confirm("Bu rozeti silmek istediğinizden emin misiniz?")) {
|
||||
setBadges(badges.filter((badge) => badge.id !== id));
|
||||
alert("Rozet başarıyla silindi!");
|
||||
if (window.confirm('Bu rozeti silmek istediğinizden emin misiniz?')) {
|
||||
setBadges(badges.filter((badge) => badge.id !== id))
|
||||
alert('Rozet başarıyla silindi!')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleRevokeBadge = (employeeBadgeId: string) => {
|
||||
if (
|
||||
window.confirm(
|
||||
"Bu rozet atamasını iptal etmek istediğinizden emin misiniz?"
|
||||
)
|
||||
) {
|
||||
if (window.confirm('Bu rozet atamasını iptal etmek istediğinizden emin misiniz?')) {
|
||||
setEmployeeBadges((prev) =>
|
||||
prev.map((eb) =>
|
||||
eb.id === employeeBadgeId ? { ...eb, isActive: false } : eb
|
||||
)
|
||||
);
|
||||
alert("Rozet ataması iptal edildi!");
|
||||
prev.map((eb) => (eb.id === employeeBadgeId ? { ...eb, isActive: false } : eb)),
|
||||
)
|
||||
alert('Rozet ataması iptal edildi!')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleBadgeSubmit = (badgeData: HrBadge) => {
|
||||
if (editingBadge) {
|
||||
|
|
@ -71,29 +62,27 @@ const BadgeManagement: React.FC = () => {
|
|||
...badgeData,
|
||||
lastModificationTime: new Date(),
|
||||
}
|
||||
: badge
|
||||
)
|
||||
);
|
||||
alert("Rozet başarıyla güncellendi!");
|
||||
: badge,
|
||||
),
|
||||
)
|
||||
alert('Rozet başarıyla güncellendi!')
|
||||
} else {
|
||||
// Create new badge
|
||||
const newBadge: HrBadge = {
|
||||
...badgeData,
|
||||
creationTime: new Date(),
|
||||
lastModificationTime: new Date(),
|
||||
};
|
||||
setBadges((prev) => [...prev, newBadge]);
|
||||
alert("Yeni rozet başarıyla oluşturuldu!");
|
||||
}
|
||||
setBadges((prev) => [...prev, newBadge])
|
||||
alert('Yeni rozet başarıyla oluşturuldu!')
|
||||
}
|
||||
setIsBadgeModalOpen(false);
|
||||
setEditingBadge(null);
|
||||
};
|
||||
setIsBadgeModalOpen(false)
|
||||
setEditingBadge(null)
|
||||
}
|
||||
|
||||
const handleAssignmentSubmit = (assignmentData: BadgeAssignmentFormData) => {
|
||||
const employee = mockEmployees.find(
|
||||
(emp) => emp.id === assignmentData.employeeId
|
||||
);
|
||||
const badge = badges.find((b) => b.id === assignmentData.badgeId);
|
||||
const employee = mockEmployees.find((emp) => emp.id === assignmentData.employeeId)
|
||||
const badge = badges.find((b) => b.id === assignmentData.badgeId)
|
||||
|
||||
if (employee && badge) {
|
||||
const newAssignment: HrEmployeeBadge = {
|
||||
|
|
@ -103,42 +92,40 @@ const BadgeManagement: React.FC = () => {
|
|||
badgeId: assignmentData.badgeId,
|
||||
badge: badge,
|
||||
earnedDate: new Date(assignmentData.earnedDate),
|
||||
expiryDate: assignmentData.expiryDate
|
||||
? new Date(assignmentData.expiryDate)
|
||||
: undefined,
|
||||
expiryDate: assignmentData.expiryDate ? new Date(assignmentData.expiryDate) : undefined,
|
||||
reason: assignmentData.reason,
|
||||
notes: assignmentData.notes,
|
||||
isActive: true,
|
||||
creationTime: new Date(),
|
||||
lastModificationTime: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
setEmployeeBadges((prev) => [...prev, newAssignment]);
|
||||
alert("Rozet başarıyla atandı!");
|
||||
setEmployeeBadges((prev) => [...prev, newAssignment])
|
||||
alert('Rozet başarıyla atandı!')
|
||||
}
|
||||
setIsAssignmentModalOpen(false);
|
||||
};
|
||||
setIsAssignmentModalOpen(false)
|
||||
}
|
||||
|
||||
const openAssignmentModal = () => {
|
||||
setIsAssignmentModalOpen(true);
|
||||
};
|
||||
setIsAssignmentModalOpen(true)
|
||||
}
|
||||
|
||||
const filteredBadges = badges.filter(
|
||||
(badge) =>
|
||||
badge.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
badge.description.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
badge.description.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
const filteredEmployeeBadges = employeeBadges.filter((eb) =>
|
||||
eb.employee?.fullName.toLowerCase().includes(searchAssignTerm.toLowerCase())
|
||||
);
|
||||
eb.employee?.fullName.toLowerCase().includes(searchAssignTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
const badgeColumns: Column<HrBadge>[] = [
|
||||
{
|
||||
key: "icon",
|
||||
header: "Simge",
|
||||
key: 'icon',
|
||||
header: 'Simge',
|
||||
render: (badge: HrBadge) => {
|
||||
const IconComponent = getIconComponent(badge.icon);
|
||||
const IconComponent = getIconComponent(badge.icon)
|
||||
return (
|
||||
<div
|
||||
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" />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
header: "Rozet Adı",
|
||||
key: 'name',
|
||||
header: 'Rozet Adı',
|
||||
sortable: true,
|
||||
render: (badge: HrBadge) => (
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{badge.name}</div>
|
||||
<div className="text-sm text-gray-500 truncate max-w-xs">
|
||||
{badge.description}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 truncate max-w-xs">{badge.description}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "criteria",
|
||||
header: "Kazanma Kriteri",
|
||||
key: 'criteria',
|
||||
header: 'Kazanma Kriteri',
|
||||
render: (badge: HrBadge) => (
|
||||
<div className="text-sm text-gray-600 max-w-xs truncate">
|
||||
{badge.criteria}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 max-w-xs truncate">{badge.criteria}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "assignedCount",
|
||||
header: "Atanan Sayısı",
|
||||
key: 'assignedCount',
|
||||
header: 'Atanan Sayısı',
|
||||
render: (badge: HrBadge) => {
|
||||
const count = employeeBadges.filter(
|
||||
(eb) => eb.badgeId === badge.id && eb.isActive
|
||||
).length;
|
||||
const count = employeeBadges.filter((eb) => eb.badgeId === badge.id && eb.isActive).length
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaUsers className="w-4 h-4 text-gray-500" />
|
||||
<span>{count}</span>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
header: "Durum",
|
||||
key: 'status',
|
||||
header: 'Durum',
|
||||
render: (badge: HrBadge) => (
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
badge.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
badge.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{badge.isActive ? "Aktif" : "Pasif"}
|
||||
{badge.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
header: "İşlemler",
|
||||
key: 'actions',
|
||||
header: 'İşlemler',
|
||||
render: (badge: HrBadge) => (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
|
|
@ -223,30 +202,24 @@ const BadgeManagement: React.FC = () => {
|
|||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
const assignmentColumns: Column<HrEmployeeBadge>[] = [
|
||||
{
|
||||
key: "employee",
|
||||
header: "Personel",
|
||||
key: 'employee',
|
||||
header: 'Personel',
|
||||
render: (assignment: HrEmployeeBadge) => (
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">
|
||||
{assignment.employee?.fullName}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{assignment.employee?.code}
|
||||
</div>
|
||||
<div className="font-medium text-gray-900">{assignment.employee?.fullName}</div>
|
||||
<div className="text-sm text-gray-500">{assignment.employee?.code}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "badge",
|
||||
header: "Rozet",
|
||||
key: 'badge',
|
||||
header: 'Rozet',
|
||||
render: (assignment: HrEmployeeBadge) => {
|
||||
const IconComponent = getIconComponent(
|
||||
assignment.badge?.icon || "award"
|
||||
);
|
||||
const IconComponent = getIconComponent(assignment.badge?.icon || 'award')
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
|
|
@ -257,51 +230,46 @@ const BadgeManagement: React.FC = () => {
|
|||
</div>
|
||||
<span>{assignment.badge?.name}</span>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "earnedDate",
|
||||
header: "Kazanıldığı Tarih",
|
||||
key: 'earnedDate',
|
||||
header: 'Kazanıldığı Tarih',
|
||||
render: (assignment: HrEmployeeBadge) =>
|
||||
new Date(assignment.earnedDate).toLocaleDateString("tr-TR"),
|
||||
new Date(assignment.earnedDate).toLocaleDateString('tr-TR'),
|
||||
},
|
||||
{
|
||||
key: "expiryDate",
|
||||
header: "Geçerlilik Süresi",
|
||||
key: 'expiryDate',
|
||||
header: 'Geçerlilik Süresi',
|
||||
render: (assignment: HrEmployeeBadge) =>
|
||||
assignment.expiryDate
|
||||
? new Date(assignment.expiryDate).toLocaleDateString("tr-TR")
|
||||
: "Süresiz",
|
||||
? new Date(assignment.expiryDate).toLocaleDateString('tr-TR')
|
||||
: 'Süresiz',
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
header: "Durum",
|
||||
key: 'status',
|
||||
header: 'Durum',
|
||||
render: (assignment: HrEmployeeBadge) => {
|
||||
const isExpired =
|
||||
assignment.expiryDate && new Date(assignment.expiryDate) < new Date();
|
||||
const isExpired = assignment.expiryDate && new Date(assignment.expiryDate) < new Date()
|
||||
return (
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
!assignment.isActive
|
||||
? "bg-red-100 text-red-800"
|
||||
? 'bg-red-100 text-red-800'
|
||||
: isExpired
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-green-100 text-green-800"
|
||||
? 'bg-yellow-100 text-yellow-800'
|
||||
: 'bg-green-100 text-green-800'
|
||||
}`}
|
||||
>
|
||||
{!assignment.isActive
|
||||
? "İptal Edildi"
|
||||
: isExpired
|
||||
? "Süresi Dolmuş"
|
||||
: "Aktif"}
|
||||
{!assignment.isActive ? 'İptal Edildi' : isExpired ? 'Süresi Dolmuş' : 'Aktif'}
|
||||
</span>
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
header: "İşlemler",
|
||||
key: 'actions',
|
||||
header: 'İşlemler',
|
||||
render: (assignment: HrEmployeeBadge) => (
|
||||
<div className="flex gap-2">
|
||||
{assignment.isActive && (
|
||||
|
|
@ -316,156 +284,145 @@ const BadgeManagement: React.FC = () => {
|
|||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-gray-900">Rozet Yönetimi</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
Personel başarı rozetleri ve ödüllendirme sistemi
|
||||
</p>
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-gray-900">Rozet Yönetimi</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
Personel başarı rozetleri ve ödüllendirme sistemi
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<Widget
|
||||
title="Toplam Rozet"
|
||||
value={badges.length}
|
||||
color="blue"
|
||||
icon="FaAward"
|
||||
/>
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<Widget title="Toplam Rozet" value={badges.length} color="blue" icon="FaAward" />
|
||||
|
||||
<Widget
|
||||
title="Aktif Rozet"
|
||||
value={badges.filter((b) => b.isActive).length}
|
||||
color="yellow"
|
||||
icon="FaTrophy"
|
||||
/>
|
||||
<Widget
|
||||
title="Aktif Rozet"
|
||||
value={badges.filter((b) => b.isActive).length}
|
||||
color="yellow"
|
||||
icon="FaTrophy"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Toplam Atama"
|
||||
value={filteredEmployeeBadges.length}
|
||||
color="green"
|
||||
icon="FaUsers"
|
||||
/>
|
||||
<Widget
|
||||
title="Toplam Atama"
|
||||
value={filteredEmployeeBadges.length}
|
||||
color="green"
|
||||
icon="FaUsers"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Rozetli Personel"
|
||||
value={
|
||||
new Set(filteredEmployeeBadges.map((ab) => ab.employeeId)).size
|
||||
}
|
||||
color="purple"
|
||||
icon="FaStar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex space-x-8">
|
||||
<button
|
||||
onClick={() => setActiveTab("badges")}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === "badges"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
Rozetler
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("assignments")}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === "assignments"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
Rozet Atamaları
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
{activeTab === "badges" && (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Search */}
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Rozet adı veya açıklama ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<Widget
|
||||
title="Rozetli Personel"
|
||||
value={new Set(filteredEmployeeBadges.map((ab) => ab.employeeId)).size}
|
||||
color="purple"
|
||||
icon="FaStar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex space-x-8">
|
||||
<button
|
||||
onClick={handleAddBadge}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
onClick={() => setActiveTab('badges')}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'badges'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
<span className="whitespace-nowrap">Yeni Rozet</span>
|
||||
Rozetler
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Badges Table */}
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
<DataTable data={filteredBadges} columns={badgeColumns} />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setActiveTab('assignments')}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'assignments'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Rozet Atamaları
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "assignments" && (
|
||||
<div className="space-y-3 pt-2">
|
||||
<div className="bg-white p-3 rounded-lg shadow-sm border">
|
||||
{/* Tab Content */}
|
||||
{activeTab === 'badges' && (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Search */}
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Rozet adı veya açıklama ara..."
|
||||
value={searchAssignTerm}
|
||||
onChange={(e) => setSearchAssignTerm(e.target.value)}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
<button
|
||||
className="px-3 py-1 text-sm bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors"
|
||||
onClick={openAssignmentModal}
|
||||
onClick={handleAddBadge}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<span className="whitespace-nowrap">Rozet Ata</span>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
<span className="whitespace-nowrap">Yeni Rozet</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Assignments Table */}
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
<DataTable
|
||||
data={filteredEmployeeBadges}
|
||||
columns={assignmentColumns}
|
||||
/>
|
||||
{/* Badges Table */}
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
<DataTable data={filteredBadges} columns={badgeColumns} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{(activeTab === "badges" ? filteredBadges : filteredEmployeeBadges)
|
||||
.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaAward className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
||||
{activeTab === "badges"
|
||||
? "Rozet bulunamadı"
|
||||
: "Rozet ataması bulunamadı"}
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
{activeTab === "badges"
|
||||
? "Henüz rozet tanımlanmamış."
|
||||
: "Henüz rozet ataması yapılmamış."}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 'assignments' && (
|
||||
<div className="space-y-3 pt-2">
|
||||
<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">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Rozet adı veya açıklama ara..."
|
||||
value={searchAssignTerm}
|
||||
onChange={(e) => setSearchAssignTerm(e.target.value)}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
<button
|
||||
className="px-3 py-1 text-sm bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors"
|
||||
onClick={openAssignmentModal}
|
||||
>
|
||||
<span className="whitespace-nowrap">Rozet Ata</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Assignments Table */}
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
<DataTable data={filteredEmployeeBadges} columns={assignmentColumns} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(activeTab === 'badges' ? filteredBadges : filteredEmployeeBadges).length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaAward className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
||||
{activeTab === 'badges' ? 'Rozet bulunamadı' : 'Rozet ataması bulunamadı'}
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
{activeTab === 'badges'
|
||||
? 'Henüz rozet tanımlanmamış.'
|
||||
: 'Henüz rozet ataması yapılmamış.'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Badge Edit Modal */}
|
||||
<BadgeEditModal
|
||||
|
|
@ -473,7 +430,7 @@ const BadgeManagement: React.FC = () => {
|
|||
onClose={() => setIsBadgeModalOpen(false)}
|
||||
badge={editingBadge}
|
||||
onSubmit={handleBadgeSubmit}
|
||||
mode={editingBadge ? "edit" : "create"}
|
||||
mode={editingBadge ? 'edit' : 'create'}
|
||||
/>
|
||||
|
||||
{/* Badge Assignment Modal */}
|
||||
|
|
@ -482,8 +439,8 @@ const BadgeManagement: React.FC = () => {
|
|||
onClose={() => setIsAssignmentModalOpen(false)}
|
||||
onSubmit={handleAssignmentSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default BadgeManagement;
|
||||
export default BadgeManagement
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
FaPlus,
|
||||
FaEdit,
|
||||
|
|
@ -9,64 +9,54 @@ import {
|
|||
FaEye,
|
||||
FaList,
|
||||
FaTh,
|
||||
} from "react-icons/fa";
|
||||
import { HrCostCenter, CostCenterType } from "../../../types/hr";
|
||||
import DataTable, { Column } from "../../../components/common/DataTable";
|
||||
import { mockCostCenters } from "../../../mocks/mockCostCenters";
|
||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||||
import CostCenterFormModal from "./CostCenterFormModal";
|
||||
import CostCenterViewModal from "./CostCenterViewModal";
|
||||
import Widget from "../../../components/common/Widget";
|
||||
import {
|
||||
getCostCenterTypeColor,
|
||||
getCostCenterTypeText,
|
||||
} from "../../../utils/erp";
|
||||
} from 'react-icons/fa'
|
||||
import { HrCostCenter, CostCenterType } from '../../../types/hr'
|
||||
import DataTable, { Column } from '../../../components/common/DataTable'
|
||||
import { mockCostCenters } from '../../../mocks/mockCostCenters'
|
||||
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||
import CostCenterFormModal from './CostCenterFormModal'
|
||||
import CostCenterViewModal from './CostCenterViewModal'
|
||||
import Widget from '../../../components/common/Widget'
|
||||
import { getCostCenterTypeColor, getCostCenterTypeText } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const CostCenterManagement: React.FC = () => {
|
||||
const [costCenters, setCostCenters] =
|
||||
useState<HrCostCenter[]>(mockCostCenters);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedType, setSelectedType] = useState<string>("all");
|
||||
const [viewMode, setViewMode] = useState<"list" | "cards">("list");
|
||||
const [isFormModalOpen, setIsFormModalOpen] = useState(false);
|
||||
const [isViewModalOpen, setIsViewModalOpen] = useState(false);
|
||||
const [selectedCostCenter, setSelectedCostCenter] =
|
||||
useState<HrCostCenter | null>(null);
|
||||
const [editingCostCenter, setEditingCostCenter] = useState<
|
||||
HrCostCenter | undefined
|
||||
>(undefined);
|
||||
const [costCenters, setCostCenters] = useState<HrCostCenter[]>(mockCostCenters)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedType, setSelectedType] = useState<string>('all')
|
||||
const [viewMode, setViewMode] = useState<'list' | 'cards'>('list')
|
||||
const [isFormModalOpen, setIsFormModalOpen] = useState(false)
|
||||
const [isViewModalOpen, setIsViewModalOpen] = useState(false)
|
||||
const [selectedCostCenter, setSelectedCostCenter] = useState<HrCostCenter | null>(null)
|
||||
const [editingCostCenter, setEditingCostCenter] = useState<HrCostCenter | undefined>(undefined)
|
||||
|
||||
// Cost center bağlantılarını kur
|
||||
mockCostCenters.forEach((cc) => {
|
||||
if (cc.responsibleEmployeeId) {
|
||||
cc.responsibleEmployee = mockEmployees.find(
|
||||
(emp) => emp.id === cc.responsibleEmployeeId
|
||||
);
|
||||
cc.responsibleEmployee = mockEmployees.find((emp) => emp.id === cc.responsibleEmployeeId)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
setEditingCostCenter(undefined);
|
||||
setIsFormModalOpen(true);
|
||||
};
|
||||
setEditingCostCenter(undefined)
|
||||
setIsFormModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEdit = (costCenter: HrCostCenter) => {
|
||||
setEditingCostCenter(costCenter);
|
||||
setIsFormModalOpen(true);
|
||||
};
|
||||
setEditingCostCenter(costCenter)
|
||||
setIsFormModalOpen(true)
|
||||
}
|
||||
|
||||
const handleView = (costCenter: HrCostCenter) => {
|
||||
setSelectedCostCenter(costCenter);
|
||||
setIsViewModalOpen(true);
|
||||
};
|
||||
setSelectedCostCenter(costCenter)
|
||||
setIsViewModalOpen(true)
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if (
|
||||
window.confirm("Bu maliyet merkezini silmek istediğinizden emin misiniz?")
|
||||
) {
|
||||
setCostCenters(costCenters.filter((cc) => cc.id !== id));
|
||||
if (window.confirm('Bu maliyet merkezini silmek istediğinizden emin misiniz?')) {
|
||||
setCostCenters(costCenters.filter((cc) => cc.id !== id))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleSave = (costCenterData: Partial<HrCostCenter>) => {
|
||||
if (editingCostCenter) {
|
||||
|
|
@ -79,9 +69,9 @@ const CostCenterManagement: React.FC = () => {
|
|||
...costCenterData,
|
||||
lastModificationTime: new Date(),
|
||||
}
|
||||
: cc
|
||||
)
|
||||
);
|
||||
: cc,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// Add new
|
||||
const newCostCenter: HrCostCenter = {
|
||||
|
|
@ -90,12 +80,12 @@ const CostCenterManagement: React.FC = () => {
|
|||
subCostCenters: [],
|
||||
creationTime: new Date(),
|
||||
lastModificationTime: new Date(),
|
||||
} as HrCostCenter;
|
||||
setCostCenters([...costCenters, newCostCenter]);
|
||||
} as HrCostCenter
|
||||
setCostCenters([...costCenters, newCostCenter])
|
||||
}
|
||||
setIsFormModalOpen(false);
|
||||
setEditingCostCenter(undefined);
|
||||
};
|
||||
setIsFormModalOpen(false)
|
||||
setEditingCostCenter(undefined)
|
||||
}
|
||||
|
||||
const filteredCostCenters = costCenters.filter((costCenter) => {
|
||||
if (
|
||||
|
|
@ -103,37 +93,37 @@ const CostCenterManagement: React.FC = () => {
|
|||
!costCenter.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
|
||||
!costCenter.code.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (selectedType !== "all" && costCenter.costCenterType !== selectedType) {
|
||||
return false;
|
||||
if (selectedType !== 'all' && costCenter.costCenterType !== selectedType) {
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
const getVariancePercentage = (budgeted: number, actual: number): number => {
|
||||
if (budgeted === 0) return 0;
|
||||
return ((actual - budgeted) / budgeted) * 100;
|
||||
};
|
||||
if (budgeted === 0) return 0
|
||||
return ((actual - budgeted) / budgeted) * 100
|
||||
}
|
||||
|
||||
const columns: Column<HrCostCenter>[] = [
|
||||
{
|
||||
key: "code",
|
||||
header: "Maliyet Merkezi Kodu",
|
||||
key: 'code',
|
||||
header: 'Maliyet Merkezi Kodu',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
header: "Maliyet Merkezi Adı",
|
||||
key: 'name',
|
||||
header: 'Maliyet Merkezi Adı',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "costCenterType",
|
||||
header: "Tip",
|
||||
key: 'costCenterType',
|
||||
header: 'Tip',
|
||||
render: (costCenter: HrCostCenter) => (
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${getCostCenterTypeColor(
|
||||
costCenter.costCenterType
|
||||
costCenter.costCenterType,
|
||||
)}`}
|
||||
>
|
||||
{getCostCenterTypeText(costCenter.costCenterType)}
|
||||
|
|
@ -141,14 +131,13 @@ const CostCenterManagement: React.FC = () => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "responsibleEmployee",
|
||||
header: "Sorumlu",
|
||||
render: (costCenter: HrCostCenter) =>
|
||||
costCenter.responsibleEmployee?.fullName || "-",
|
||||
key: 'responsibleEmployee',
|
||||
header: 'Sorumlu',
|
||||
render: (costCenter: HrCostCenter) => costCenter.responsibleEmployee?.fullName || '-',
|
||||
},
|
||||
{
|
||||
key: "budgetedAmount",
|
||||
header: "Bütçe",
|
||||
key: 'budgetedAmount',
|
||||
header: 'Bütçe',
|
||||
render: (costCenter: HrCostCenter) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaDollarSign className="w-4 h-4 text-gray-500" />
|
||||
|
|
@ -157,8 +146,8 @@ const CostCenterManagement: React.FC = () => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "actualAmount",
|
||||
header: "Gerçekleşen",
|
||||
key: 'actualAmount',
|
||||
header: 'Gerçekleşen',
|
||||
render: (costCenter: HrCostCenter) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaDollarSign className="w-4 h-4 text-gray-500" />
|
||||
|
|
@ -167,47 +156,38 @@ const CostCenterManagement: React.FC = () => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "variance",
|
||||
header: "Fark (%)",
|
||||
key: 'variance',
|
||||
header: 'Fark (%)',
|
||||
render: (costCenter: HrCostCenter) => {
|
||||
const variance = getVariancePercentage(
|
||||
costCenter.budgetedAmount,
|
||||
costCenter.actualAmount
|
||||
);
|
||||
const isPositive = variance > 0;
|
||||
const variance = getVariancePercentage(costCenter.budgetedAmount, costCenter.actualAmount)
|
||||
const isPositive = variance > 0
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaPercentage className="w-4 h-4 text-gray-500" />
|
||||
<span
|
||||
className={`font-medium ${
|
||||
isPositive ? "text-red-600" : "text-green-600"
|
||||
}`}
|
||||
>
|
||||
{isPositive ? "+" : ""}
|
||||
<span className={`font-medium ${isPositive ? 'text-red-600' : 'text-green-600'}`}>
|
||||
{isPositive ? '+' : ''}
|
||||
{variance.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
header: "Durum",
|
||||
key: 'status',
|
||||
header: 'Durum',
|
||||
render: (costCenter: HrCostCenter) => (
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
costCenter.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
costCenter.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{costCenter.isActive ? "Aktif" : "Pasif"}
|
||||
{costCenter.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
header: "İşlemler",
|
||||
key: 'actions',
|
||||
header: 'İşlemler',
|
||||
render: (costCenter: HrCostCenter) => (
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
|
|
@ -234,23 +214,17 @@ const CostCenterManagement: React.FC = () => {
|
|||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
const totalBudget = costCenters.reduce(
|
||||
(sum, cc) => sum + cc.budgetedAmount,
|
||||
0
|
||||
);
|
||||
const totalActual = costCenters.reduce((sum, cc) => sum + cc.actualAmount, 0);
|
||||
const totalVariance = getVariancePercentage(totalBudget, totalActual);
|
||||
const totalBudget = costCenters.reduce((sum, cc) => sum + cc.budgetedAmount, 0)
|
||||
const totalActual = costCenters.reduce((sum, cc) => sum + cc.actualAmount, 0)
|
||||
const totalVariance = getVariancePercentage(totalBudget, totalActual)
|
||||
|
||||
const renderCards = () => (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{filteredCostCenters.map((costCenter) => {
|
||||
const variance = getVariancePercentage(
|
||||
costCenter.budgetedAmount,
|
||||
costCenter.actualAmount
|
||||
);
|
||||
const isPositive = variance > 0;
|
||||
const variance = getVariancePercentage(costCenter.budgetedAmount, costCenter.actualAmount)
|
||||
const isPositive = variance > 0
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -260,14 +234,12 @@ const CostCenterManagement: React.FC = () => {
|
|||
<div className="p-4">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
||||
{costCenter.name}
|
||||
</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">{costCenter.name}</h3>
|
||||
<p className="text-sm text-gray-600">{costCenter.code}</p>
|
||||
</div>
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${getCostCenterTypeColor(
|
||||
costCenter.costCenterType
|
||||
costCenter.costCenterType,
|
||||
)}`}
|
||||
>
|
||||
{getCostCenterTypeText(costCenter.costCenterType)}
|
||||
|
|
@ -275,32 +247,22 @@ const CostCenterManagement: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{costCenter.description && (
|
||||
<p className="text-sm text-gray-600 mb-4 line-clamp-2">
|
||||
{costCenter.description}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 mb-4 line-clamp-2">{costCenter.description}</p>
|
||||
)}
|
||||
|
||||
<div className="space-y-3 mb-4">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-gray-600">Bütçe:</span>
|
||||
<span className="font-medium">
|
||||
₺{costCenter.budgetedAmount.toLocaleString()}
|
||||
</span>
|
||||
<span className="font-medium">₺{costCenter.budgetedAmount.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-gray-600">Gerçekleşen:</span>
|
||||
<span className="font-medium">
|
||||
₺{costCenter.actualAmount.toLocaleString()}
|
||||
</span>
|
||||
<span className="font-medium">₺{costCenter.actualAmount.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-gray-600">Fark:</span>
|
||||
<span
|
||||
className={`font-medium ${
|
||||
isPositive ? "text-red-600" : "text-green-600"
|
||||
}`}
|
||||
>
|
||||
{isPositive ? "+" : ""}
|
||||
<span className={`font-medium ${isPositive ? 'text-red-600' : 'text-green-600'}`}>
|
||||
{isPositive ? '+' : ''}
|
||||
{variance.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -318,12 +280,10 @@ const CostCenterManagement: React.FC = () => {
|
|||
<div className="flex items-center justify-between pt-4 border-t">
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
costCenter.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
costCenter.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{costCenter.isActive ? "Aktif" : "Pasif"}
|
||||
{costCenter.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
|
||||
<div className="flex gap-1">
|
||||
|
|
@ -352,162 +312,156 @@ const CostCenterManagement: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">
|
||||
Maliyet Merkezi Yönetimi
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Maliyet merkezlerini ve bütçe takibini yönetin
|
||||
</p>
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">Maliyet Merkezi Yönetimi</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Maliyet merkezlerini ve bütçe takibini yönetin
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Maliyet Merkezi
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Maliyet Merkezi
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Widget
|
||||
title="Toplam Maliyet Merkezi"
|
||||
value={costCenters.length}
|
||||
color="blue"
|
||||
icon="FaChartPie"
|
||||
/>
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Widget
|
||||
title="Toplam Maliyet Merkezi"
|
||||
value={costCenters.length}
|
||||
color="blue"
|
||||
icon="FaChartPie"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Toplam Bütçe"
|
||||
value={`₺${totalBudget.toLocaleString()}`}
|
||||
color="green"
|
||||
icon="FaDollarSign"
|
||||
/>
|
||||
<Widget
|
||||
title="Toplam Bütçe"
|
||||
value={`₺${totalBudget.toLocaleString()}`}
|
||||
color="green"
|
||||
icon="FaDollarSign"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Toplam Gerçekleşen"
|
||||
value={`₺${totalActual.toLocaleString()}`}
|
||||
color="orange"
|
||||
icon="FaDollarSign"
|
||||
/>
|
||||
<Widget
|
||||
title="Toplam Gerçekleşen"
|
||||
value={`₺${totalActual.toLocaleString()}`}
|
||||
color="orange"
|
||||
icon="FaDollarSign"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Toplam Fark"
|
||||
value={`${totalVariance > 0 ? "+" : ""}${totalVariance.toFixed(1)}%`}
|
||||
color={totalVariance > 0 ? "red" : "green"}
|
||||
icon="FaPercentage"
|
||||
/>
|
||||
</div>
|
||||
<Widget
|
||||
title="Toplam Fark"
|
||||
value={`${totalVariance > 0 ? '+' : ''}${totalVariance.toFixed(1)}%`}
|
||||
color={totalVariance > 0 ? 'red' : 'green'}
|
||||
icon="FaPercentage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Filters and View Mode */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-stretch sm:items-center justify-between">
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-stretch sm:items-center flex-1">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Maliyet merkezi adı veya kodu ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
{/* Filters and View Mode */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-stretch sm:items-center justify-between">
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-stretch sm:items-center flex-1">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Maliyet merkezi adı veya kodu ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<select
|
||||
value={selectedType}
|
||||
onChange={(e) => setSelectedType(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[200px]"
|
||||
>
|
||||
<option value="all">Tüm Tipler</option>
|
||||
{Object.values(CostCenterType).map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{getCostCenterTypeText(type)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<select
|
||||
value={selectedType}
|
||||
onChange={(e) => setSelectedType(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[200px]"
|
||||
>
|
||||
<option value="all">Tüm Tipler</option>
|
||||
{Object.values(CostCenterType).map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{getCostCenterTypeText(type)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex items-center gap-2 bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-1.5 rounded-md text-sm transition-colors ${
|
||||
viewMode === 'list'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('cards')}
|
||||
className={`p-1.5 rounded-md text-sm transition-colors ${
|
||||
viewMode === 'cards'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex items-center gap-2 bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
className={`p-1.5 rounded-md text-sm transition-colors ${
|
||||
viewMode === "list"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-600 hover:text-gray-900"
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("cards")}
|
||||
className={`p-1.5 rounded-md text-sm transition-colors ${
|
||||
viewMode === "cards"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-600 hover:text-gray-900"
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Content */}
|
||||
{viewMode === 'list' ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
<DataTable data={filteredCostCenters} columns={columns} />
|
||||
</div>
|
||||
) : (
|
||||
renderCards()
|
||||
)}
|
||||
|
||||
{filteredCostCenters.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaChartPie className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">Maliyet merkezi bulunamadı</h3>
|
||||
<p className="text-gray-500">Arama kriterlerinizi değiştirmeyi deneyin.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
{viewMode === "list" ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
<DataTable data={filteredCostCenters} columns={columns} />
|
||||
</div>
|
||||
) : (
|
||||
renderCards()
|
||||
)}
|
||||
|
||||
{filteredCostCenters.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaChartPie className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
||||
Maliyet merkezi bulunamadı
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
Arama kriterlerinizi değiştirmeyi deneyin.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modals */}
|
||||
<CostCenterFormModal
|
||||
isOpen={isFormModalOpen}
|
||||
onClose={() => {
|
||||
setIsFormModalOpen(false);
|
||||
setEditingCostCenter(undefined);
|
||||
setIsFormModalOpen(false)
|
||||
setEditingCostCenter(undefined)
|
||||
}}
|
||||
onSave={handleSave}
|
||||
costCenter={editingCostCenter}
|
||||
title={
|
||||
editingCostCenter ? "Maliyet Merkezi Düzenle" : "Yeni Maliyet Merkezi"
|
||||
}
|
||||
title={editingCostCenter ? 'Maliyet Merkezi Düzenle' : 'Yeni Maliyet Merkezi'}
|
||||
/>
|
||||
|
||||
<CostCenterViewModal
|
||||
isOpen={isViewModalOpen}
|
||||
onClose={() => {
|
||||
setIsViewModalOpen(false);
|
||||
setSelectedCostCenter(null);
|
||||
setIsViewModalOpen(false)
|
||||
setSelectedCostCenter(null)
|
||||
}}
|
||||
costCenter={selectedCostCenter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default CostCenterManagement;
|
||||
export default CostCenterManagement
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
FaEye,
|
||||
FaEdit,
|
||||
FaPlus,
|
||||
FaTimes,
|
||||
FaSave,
|
||||
FaTrash,
|
||||
FaQuestionCircle,
|
||||
} from "react-icons/fa";
|
||||
import React, { useState } from 'react'
|
||||
import { FaEye, FaEdit, FaPlus, FaTimes, FaSave, FaTrash, FaQuestionCircle } from 'react-icons/fa'
|
||||
import {
|
||||
HrEvaluation360Template,
|
||||
HrQuestionGroup,
|
||||
|
|
@ -15,77 +7,73 @@ import {
|
|||
QuestionTypeEnum,
|
||||
HrQuestionOption,
|
||||
AssessorTypeEnum,
|
||||
} from "../../../types/hr";
|
||||
import DataTable, { Column } from "../../../components/common/DataTable";
|
||||
import { mockEvaluation360Templates } from "../../../mocks/mockEvaluation360Templates";
|
||||
import Widget from "../../../components/common/Widget";
|
||||
} from '../../../types/hr'
|
||||
import DataTable, { Column } from '../../../components/common/DataTable'
|
||||
import { mockEvaluation360Templates } from '../../../mocks/mockEvaluation360Templates'
|
||||
import Widget from '../../../components/common/Widget'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const Degree360Templates: React.FC = () => {
|
||||
const [templateSearchTerm, setTemplateSearchTerm] = useState("");
|
||||
const [templateSearchTerm, setTemplateSearchTerm] = useState('')
|
||||
|
||||
// Modal states
|
||||
const [showTemplateModal, setShowTemplateModal] = useState(false);
|
||||
const [showQuestionModal, setShowQuestionModal] = useState(false);
|
||||
const [showQuestionItemModal, setShowQuestionItemModal] = useState(false);
|
||||
const [selectedTemplate, setSelectedTemplate] =
|
||||
useState<HrEvaluation360Template | null>(null);
|
||||
const [selectedQuestionGroup, setSelectedQuestionGroup] =
|
||||
useState<HrQuestionGroup | null>(null);
|
||||
const [selectedQuestion, setSelectedQuestion] =
|
||||
useState<HrEvaluationQuestion | null>(null);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [showTemplateModal, setShowTemplateModal] = useState(false)
|
||||
const [showQuestionModal, setShowQuestionModal] = useState(false)
|
||||
const [showQuestionItemModal, setShowQuestionItemModal] = useState(false)
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<HrEvaluation360Template | null>(null)
|
||||
const [selectedQuestionGroup, setSelectedQuestionGroup] = useState<HrQuestionGroup | null>(null)
|
||||
const [selectedQuestion, setSelectedQuestion] = useState<HrEvaluationQuestion | null>(null)
|
||||
const [isEditMode, setIsEditMode] = useState(false)
|
||||
|
||||
// Form states
|
||||
const [templateForm, setTemplateForm] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
name: '',
|
||||
description: '',
|
||||
isActive: true,
|
||||
assessorTypes: [] as AssessorTypeEnum[],
|
||||
});
|
||||
})
|
||||
|
||||
const [questionGroupForm, setQuestionGroupForm] = useState({
|
||||
groupName: "",
|
||||
description: "",
|
||||
groupName: '',
|
||||
description: '',
|
||||
weight: 0,
|
||||
order: 0,
|
||||
});
|
||||
})
|
||||
|
||||
const [questionForm, setQuestionForm] = useState({
|
||||
questionText: "",
|
||||
questionText: '',
|
||||
questionType: QuestionTypeEnum.Rating,
|
||||
isRequired: true,
|
||||
order: 0,
|
||||
weight: 0,
|
||||
options: [] as HrQuestionOption[],
|
||||
});
|
||||
})
|
||||
|
||||
// Filter templates
|
||||
const filteredTemplates = mockEvaluation360Templates.filter((template) => {
|
||||
const searchLower = templateSearchTerm.toLowerCase();
|
||||
const searchLower = templateSearchTerm.toLowerCase()
|
||||
return (
|
||||
template.name.toLowerCase().includes(searchLower) ||
|
||||
template.description.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
// Statistics
|
||||
const totalTemplates = mockEvaluation360Templates.length;
|
||||
const activeTemplates = mockEvaluation360Templates.filter(
|
||||
(t) => t.isActive
|
||||
).length;
|
||||
const totalTemplates = mockEvaluation360Templates.length
|
||||
const activeTemplates = mockEvaluation360Templates.filter((t) => t.isActive).length
|
||||
|
||||
// Event handlers
|
||||
const handleNewTemplate = () => {
|
||||
setTemplateForm({
|
||||
name: "",
|
||||
description: "",
|
||||
name: '',
|
||||
description: '',
|
||||
isActive: true,
|
||||
assessorTypes: [],
|
||||
});
|
||||
setSelectedTemplate(null);
|
||||
setIsEditMode(false);
|
||||
setShowTemplateModal(true);
|
||||
};
|
||||
})
|
||||
setSelectedTemplate(null)
|
||||
setIsEditMode(false)
|
||||
setShowTemplateModal(true)
|
||||
}
|
||||
|
||||
const handleEditTemplate = (template: HrEvaluation360Template) => {
|
||||
setTemplateForm({
|
||||
|
|
@ -93,11 +81,11 @@ const Degree360Templates: React.FC = () => {
|
|||
description: template.description,
|
||||
isActive: template.isActive,
|
||||
assessorTypes: template.assessorTypes || [],
|
||||
});
|
||||
setSelectedTemplate(template);
|
||||
setIsEditMode(true);
|
||||
setShowTemplateModal(true);
|
||||
};
|
||||
})
|
||||
setSelectedTemplate(template)
|
||||
setIsEditMode(true)
|
||||
setShowTemplateModal(true)
|
||||
}
|
||||
|
||||
const handleViewTemplate = (template: HrEvaluation360Template) => {
|
||||
setTemplateForm({
|
||||
|
|
@ -105,72 +93,65 @@ const Degree360Templates: React.FC = () => {
|
|||
description: template.description,
|
||||
isActive: template.isActive,
|
||||
assessorTypes: template.assessorTypes || [],
|
||||
});
|
||||
setSelectedTemplate(template);
|
||||
setShowTemplateModal(true);
|
||||
setIsEditMode(false);
|
||||
};
|
||||
})
|
||||
setSelectedTemplate(template)
|
||||
setShowTemplateModal(true)
|
||||
setIsEditMode(false)
|
||||
}
|
||||
|
||||
const handleSaveTemplate = () => {
|
||||
console.log("Saving template:", templateForm);
|
||||
setShowTemplateModal(false);
|
||||
};
|
||||
console.log('Saving template:', templateForm)
|
||||
setShowTemplateModal(false)
|
||||
}
|
||||
|
||||
const handleDeleteTemplate = (template: HrEvaluation360Template) => {
|
||||
if (
|
||||
confirm(
|
||||
`"${template.name}" şablonunu silmek istediğinizden emin misiniz?`
|
||||
)
|
||||
) {
|
||||
console.log("Deleting template:", template.id);
|
||||
if (confirm(`"${template.name}" şablonunu silmek istediğinizden emin misiniz?`)) {
|
||||
console.log('Deleting template:', template.id)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleAddQuestionGroup = () => {
|
||||
setQuestionGroupForm({
|
||||
groupName: "",
|
||||
description: "",
|
||||
groupName: '',
|
||||
description: '',
|
||||
weight: 0,
|
||||
order: 0,
|
||||
});
|
||||
setSelectedQuestionGroup(null);
|
||||
setShowQuestionModal(true);
|
||||
};
|
||||
})
|
||||
setSelectedQuestionGroup(null)
|
||||
setShowQuestionModal(true)
|
||||
}
|
||||
|
||||
const handleEditQuestionGroup = (group: HrQuestionGroup) => {
|
||||
setQuestionGroupForm({
|
||||
groupName: group.groupName,
|
||||
description: group.description || "",
|
||||
description: group.description || '',
|
||||
weight: group.weight,
|
||||
order: group.order,
|
||||
});
|
||||
setSelectedQuestionGroup(group);
|
||||
setShowQuestionModal(true);
|
||||
};
|
||||
})
|
||||
setSelectedQuestionGroup(group)
|
||||
setShowQuestionModal(true)
|
||||
}
|
||||
|
||||
const handleSaveQuestionGroup = () => {
|
||||
console.log("Saving question group:", questionGroupForm);
|
||||
setShowQuestionModal(false);
|
||||
};
|
||||
console.log('Saving question group:', questionGroupForm)
|
||||
setShowQuestionModal(false)
|
||||
}
|
||||
|
||||
const handleAddQuestion = (group: HrQuestionGroup) => {
|
||||
setQuestionForm({
|
||||
questionText: "",
|
||||
questionText: '',
|
||||
questionType: QuestionTypeEnum.Rating,
|
||||
isRequired: true,
|
||||
order: 0,
|
||||
weight: 0,
|
||||
options: [],
|
||||
});
|
||||
setSelectedQuestion(null);
|
||||
setSelectedQuestionGroup(group);
|
||||
setShowQuestionItemModal(true);
|
||||
};
|
||||
})
|
||||
setSelectedQuestion(null)
|
||||
setSelectedQuestionGroup(group)
|
||||
setShowQuestionItemModal(true)
|
||||
}
|
||||
|
||||
const handleEditQuestion = (
|
||||
question: HrEvaluationQuestion,
|
||||
group: HrQuestionGroup
|
||||
) => {
|
||||
const handleEditQuestion = (question: HrEvaluationQuestion, group: HrQuestionGroup) => {
|
||||
setQuestionForm({
|
||||
questionText: question.questionText,
|
||||
questionType: question.questionType,
|
||||
|
|
@ -178,22 +159,22 @@ const Degree360Templates: React.FC = () => {
|
|||
order: question.order,
|
||||
weight: question.weight,
|
||||
options: question.options || [],
|
||||
});
|
||||
setSelectedQuestion(question);
|
||||
setSelectedQuestionGroup(group);
|
||||
setShowQuestionItemModal(true);
|
||||
};
|
||||
})
|
||||
setSelectedQuestion(question)
|
||||
setSelectedQuestionGroup(group)
|
||||
setShowQuestionItemModal(true)
|
||||
}
|
||||
|
||||
const handleSaveQuestion = () => {
|
||||
console.log("Saving question:", questionForm);
|
||||
setShowQuestionItemModal(false);
|
||||
};
|
||||
console.log('Saving question:', questionForm)
|
||||
setShowQuestionItemModal(false)
|
||||
}
|
||||
|
||||
// Table columns
|
||||
const templateColumns: Column<HrEvaluation360Template>[] = [
|
||||
{
|
||||
key: "name",
|
||||
header: "Şablon Adı",
|
||||
key: 'name',
|
||||
header: 'Şablon Adı',
|
||||
sortable: true,
|
||||
render: (template) => (
|
||||
<div>
|
||||
|
|
@ -203,30 +184,28 @@ const Degree360Templates: React.FC = () => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "questionGroups",
|
||||
header: "Soru Grupları",
|
||||
key: 'questionGroups',
|
||||
header: 'Soru Grupları',
|
||||
render: (template) => (
|
||||
<span className="text-sm text-gray-600">
|
||||
{template.questionGroups?.length || 0} grup
|
||||
</span>
|
||||
<span className="text-sm text-gray-600">{template.questionGroups?.length || 0} grup</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "assessorTypes",
|
||||
header: "Değerlendirecekler",
|
||||
key: 'assessorTypes',
|
||||
header: 'Değerlendirecekler',
|
||||
render: (template) => {
|
||||
const assessorLabels = {
|
||||
[AssessorTypeEnum.Self]: "Kendi",
|
||||
[AssessorTypeEnum.Manager]: "Yönetici",
|
||||
[AssessorTypeEnum.Peer]: "Meslektaş",
|
||||
[AssessorTypeEnum.Subordinate]: "Ast",
|
||||
[AssessorTypeEnum.Customer]: "Müşteri",
|
||||
[AssessorTypeEnum.OtherDepartment]: "Diğer Departman",
|
||||
[AssessorTypeEnum.HRUpperManagement]: "İK/Üst Yönetim",
|
||||
[AssessorTypeEnum.External]: "Dış Paydaş",
|
||||
};
|
||||
[AssessorTypeEnum.Self]: 'Kendi',
|
||||
[AssessorTypeEnum.Manager]: 'Yönetici',
|
||||
[AssessorTypeEnum.Peer]: 'Meslektaş',
|
||||
[AssessorTypeEnum.Subordinate]: 'Ast',
|
||||
[AssessorTypeEnum.Customer]: 'Müşteri',
|
||||
[AssessorTypeEnum.OtherDepartment]: 'Diğer Departman',
|
||||
[AssessorTypeEnum.HRUpperManagement]: 'İK/Üst Yönetim',
|
||||
[AssessorTypeEnum.External]: 'Dış Paydaş',
|
||||
}
|
||||
|
||||
const assessorTypes = template.assessorTypes || [];
|
||||
const assessorTypes = template.assessorTypes || []
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{assessorTypes.map((type) => (
|
||||
|
|
@ -241,27 +220,25 @@ const Degree360Templates: React.FC = () => {
|
|||
<span className="text-sm text-gray-400">Belirtilmemiş</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "isActive",
|
||||
header: "Durum",
|
||||
key: 'isActive',
|
||||
header: 'Durum',
|
||||
render: (template) => (
|
||||
<span
|
||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
template.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
template.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{template.isActive ? "Aktif" : "Pasif"}
|
||||
{template.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
header: "İşlemler",
|
||||
key: 'actions',
|
||||
header: 'İşlemler',
|
||||
render: (template) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
|
|
@ -288,66 +265,66 @@ const Degree360Templates: React.FC = () => {
|
|||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-4 mt-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">360° Şablonlar</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
360 derece değerlendirme şablonlarını yönetin
|
||||
</p>
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">360° Şablonlar</h2>
|
||||
<p className="text-gray-600 mt-1">360 derece değerlendirme şablonlarını yönetin</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleNewTemplate}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-purple-600 text-white rounded-md hover:bg-purple-700 ml-4 text-sm"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Şablon
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleNewTemplate}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-purple-600 text-white rounded-md hover:bg-purple-700 ml-4 text-sm"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Şablon
|
||||
</button>
|
||||
</div>
|
||||
{/* Statistics */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Widget
|
||||
title="Toplam Şablon"
|
||||
value={totalTemplates}
|
||||
color="purple"
|
||||
icon="FaClipboardList"
|
||||
/>
|
||||
|
||||
{/* Statistics */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Widget
|
||||
title="Toplam Şablon"
|
||||
value={totalTemplates}
|
||||
color="purple"
|
||||
icon="FaClipboardList"
|
||||
/>
|
||||
<Widget
|
||||
title="Aktif Şablon"
|
||||
value={activeTemplates}
|
||||
color="green"
|
||||
icon="FaClipboardList"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Aktif Şablon"
|
||||
value={activeTemplates}
|
||||
color="green"
|
||||
icon="FaClipboardList"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Pasif Şablon"
|
||||
value={totalTemplates - activeTemplates}
|
||||
color="red"
|
||||
icon="FaClipboardList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Şablon adı veya açıklama ile ara..."
|
||||
value={templateSearchTerm}
|
||||
onChange={(e) => setTemplateSearchTerm(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"
|
||||
<Widget
|
||||
title="Pasif Şablon"
|
||||
value={totalTemplates - activeTemplates}
|
||||
color="red"
|
||||
icon="FaClipboardList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Templates Table */}
|
||||
<DataTable data={filteredTemplates} columns={templateColumns} />
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Şablon adı veya açıklama ile ara..."
|
||||
value={templateSearchTerm}
|
||||
onChange={(e) => setTemplateSearchTerm(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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Templates Table */}
|
||||
<DataTable data={filteredTemplates} columns={templateColumns} />
|
||||
</div>
|
||||
|
||||
{/* Template Modal */}
|
||||
{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="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-bold">
|
||||
{isEditMode
|
||||
? "Şablon Düzenle"
|
||||
: selectedTemplate
|
||||
? "Şablon Detayı"
|
||||
: "Yeni Şablon"}
|
||||
{isEditMode ? 'Şablon Düzenle' : selectedTemplate ? 'Şablon Detayı' : 'Yeni Şablon'}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowTemplateModal(false)}
|
||||
|
|
@ -373,29 +346,23 @@ const Degree360Templates: React.FC = () => {
|
|||
{/* Basic Information */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Şablon Adı
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Şablon Adı</label>
|
||||
<input
|
||||
type="text"
|
||||
value={templateForm.name}
|
||||
onChange={(e) =>
|
||||
setTemplateForm({ ...templateForm, name: e.target.value })
|
||||
}
|
||||
onChange={(e) => 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"
|
||||
disabled={!isEditMode && !!selectedTemplate}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Durum
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Durum</label>
|
||||
<select
|
||||
value={templateForm.isActive ? "active" : "inactive"}
|
||||
value={templateForm.isActive ? 'active' : 'inactive'}
|
||||
onChange={(e) =>
|
||||
setTemplateForm({
|
||||
...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"
|
||||
|
|
@ -408,9 +375,7 @@ const Degree360Templates: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Açıklama
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Açıklama</label>
|
||||
<textarea
|
||||
value={templateForm.description}
|
||||
onChange={(e) =>
|
||||
|
|
@ -433,15 +398,15 @@ const Degree360Templates: React.FC = () => {
|
|||
<div className="grid grid-cols-2 gap-3">
|
||||
{Object.values(AssessorTypeEnum).map((type) => {
|
||||
const labels = {
|
||||
[AssessorTypeEnum.Self]: "Kendi",
|
||||
[AssessorTypeEnum.Manager]: "Yönetici",
|
||||
[AssessorTypeEnum.Peer]: "Meslektaş",
|
||||
[AssessorTypeEnum.Subordinate]: "Ast",
|
||||
[AssessorTypeEnum.Customer]: "Müşteri",
|
||||
[AssessorTypeEnum.OtherDepartment]: "Diğer Departman",
|
||||
[AssessorTypeEnum.HRUpperManagement]: "İK/Üst Yönetim",
|
||||
[AssessorTypeEnum.External]: "Dış Paydaş",
|
||||
};
|
||||
[AssessorTypeEnum.Self]: 'Kendi',
|
||||
[AssessorTypeEnum.Manager]: 'Yönetici',
|
||||
[AssessorTypeEnum.Peer]: 'Meslektaş',
|
||||
[AssessorTypeEnum.Subordinate]: 'Ast',
|
||||
[AssessorTypeEnum.Customer]: 'Müşteri',
|
||||
[AssessorTypeEnum.OtherDepartment]: 'Diğer Departman',
|
||||
[AssessorTypeEnum.HRUpperManagement]: 'İK/Üst Yönetim',
|
||||
[AssessorTypeEnum.External]: 'Dış Paydaş',
|
||||
}
|
||||
|
||||
return (
|
||||
<label key={type} className="flex items-center space-x-2">
|
||||
|
|
@ -452,29 +417,21 @@ const Degree360Templates: React.FC = () => {
|
|||
if (e.target.checked) {
|
||||
setTemplateForm({
|
||||
...templateForm,
|
||||
assessorTypes: [
|
||||
...templateForm.assessorTypes,
|
||||
type,
|
||||
],
|
||||
});
|
||||
assessorTypes: [...templateForm.assessorTypes, type],
|
||||
})
|
||||
} else {
|
||||
setTemplateForm({
|
||||
...templateForm,
|
||||
assessorTypes:
|
||||
templateForm.assessorTypes.filter(
|
||||
(t) => t !== type
|
||||
),
|
||||
});
|
||||
assessorTypes: templateForm.assessorTypes.filter((t) => t !== type),
|
||||
})
|
||||
}
|
||||
}}
|
||||
className="rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
||||
disabled={!isEditMode && !!selectedTemplate}
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{labels[type] || type}
|
||||
</span>
|
||||
<span className="text-sm text-gray-700">{labels[type] || type}</span>
|
||||
</label>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -496,93 +453,72 @@ const Degree360Templates: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{selectedTemplate.questionGroups?.map(
|
||||
(group, groupIndex) => (
|
||||
<div
|
||||
key={groupIndex}
|
||||
className="bg-gray-50 p-3 rounded-lg"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<h4 className="font-medium">{group.groupName}</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
{group.description}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
Ağırlık: {group.weight}%
|
||||
</p>
|
||||
</div>
|
||||
{isEditMode && (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleEditQuestionGroup(group)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleAddQuestion(group)}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
>
|
||||
<FaQuestionCircle className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{selectedTemplate.questionGroups?.map((group, groupIndex) => (
|
||||
<div key={groupIndex} className="bg-gray-50 p-3 rounded-lg">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<h4 className="font-medium">{group.groupName}</h4>
|
||||
<p className="text-sm text-gray-600">{group.description}</p>
|
||||
<p className="text-sm text-gray-600">Ağırlık: {group.weight}%</p>
|
||||
</div>
|
||||
|
||||
{/* Questions */}
|
||||
<div className="space-y-2">
|
||||
{group.questions?.map((question, questionIndex) => (
|
||||
<div
|
||||
key={questionIndex}
|
||||
className="bg-white p-2 rounded border"
|
||||
{isEditMode && (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleEditQuestionGroup(group)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm">
|
||||
{question.questionText}
|
||||
</p>
|
||||
<div className="flex gap-4 mt-1">
|
||||
<span className="text-xs text-gray-500">
|
||||
Tip:{" "}
|
||||
{question.questionType ===
|
||||
QuestionTypeEnum.Rating
|
||||
? "Puanlama"
|
||||
: question.questionType ===
|
||||
QuestionTypeEnum.MultipleChoice
|
||||
? "Çoktan Seçmeli"
|
||||
: question.questionType ===
|
||||
QuestionTypeEnum.Text
|
||||
? "Metin"
|
||||
: "Evet/Hayır"}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
Ağırlık: {question.weight}%
|
||||
</span>
|
||||
{question.isRequired && (
|
||||
<span className="text-xs text-red-500">
|
||||
Zorunlu
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isEditMode && (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleEditQuestion(question, group)
|
||||
}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleAddQuestion(group)}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
>
|
||||
<FaQuestionCircle className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Questions */}
|
||||
<div className="space-y-2">
|
||||
{group.questions?.map((question, questionIndex) => (
|
||||
<div key={questionIndex} className="bg-white p-2 rounded border">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm">{question.questionText}</p>
|
||||
<div className="flex gap-4 mt-1">
|
||||
<span className="text-xs text-gray-500">
|
||||
Tip:{' '}
|
||||
{question.questionType === QuestionTypeEnum.Rating
|
||||
? 'Puanlama'
|
||||
: question.questionType === QuestionTypeEnum.MultipleChoice
|
||||
? 'Çoktan Seçmeli'
|
||||
: question.questionType === QuestionTypeEnum.Text
|
||||
? 'Metin'
|
||||
: 'Evet/Hayır'}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
Ağırlık: {question.weight}%
|
||||
</span>
|
||||
{question.isRequired && (
|
||||
<span className="text-xs text-red-500">Zorunlu</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isEditMode && (
|
||||
<button
|
||||
onClick={() => handleEditQuestion(question, group)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -593,7 +529,7 @@ const Degree360Templates: React.FC = () => {
|
|||
onClick={() => setShowTemplateModal(false)}
|
||||
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>
|
||||
{(isEditMode || !selectedTemplate) && (
|
||||
<button
|
||||
|
|
@ -615,9 +551,7 @@ const Degree360Templates: React.FC = () => {
|
|||
<div className="bg-white rounded-lg p-6 w-full max-w-md">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-bold">
|
||||
{selectedQuestionGroup
|
||||
? "Soru Grubu Düzenle"
|
||||
: "Yeni Soru Grubu"}
|
||||
{selectedQuestionGroup ? 'Soru Grubu Düzenle' : 'Yeni Soru Grubu'}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowQuestionModal(false)}
|
||||
|
|
@ -629,9 +563,7 @@ const Degree360Templates: React.FC = () => {
|
|||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Grup Adı
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Grup Adı</label>
|
||||
<input
|
||||
type="text"
|
||||
value={questionGroupForm.groupName}
|
||||
|
|
@ -646,9 +578,7 @@ const Degree360Templates: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Açıklama
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Açıklama</label>
|
||||
<textarea
|
||||
value={questionGroupForm.description}
|
||||
onChange={(e) =>
|
||||
|
|
@ -682,9 +612,7 @@ const Degree360Templates: React.FC = () => {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Sıra
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Sıra</label>
|
||||
<input
|
||||
type="number"
|
||||
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="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-bold">
|
||||
{selectedQuestion ? "Soru Düzenle" : "Yeni Soru"}
|
||||
{selectedQuestion ? 'Soru Düzenle' : 'Yeni Soru'}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowQuestionItemModal(false)}
|
||||
|
|
@ -738,9 +666,7 @@ const Degree360Templates: React.FC = () => {
|
|||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Soru Metni
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Soru Metni</label>
|
||||
<textarea
|
||||
value={questionForm.questionText}
|
||||
onChange={(e) =>
|
||||
|
|
@ -755,9 +681,7 @@ const Degree360Templates: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Soru Tipi
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Soru Tipi</label>
|
||||
<select
|
||||
value={questionForm.questionType}
|
||||
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"
|
||||
>
|
||||
<option value={QuestionTypeEnum.Rating}>Puanlama</option>
|
||||
<option value={QuestionTypeEnum.MultipleChoice}>
|
||||
Çoktan Seçmeli
|
||||
</option>
|
||||
<option value={QuestionTypeEnum.MultipleChoice}>Çoktan Seçmeli</option>
|
||||
<option value={QuestionTypeEnum.Text}>Metin</option>
|
||||
<option value={QuestionTypeEnum.YesNo}>Evet/Hayır</option>
|
||||
</select>
|
||||
|
|
@ -797,9 +719,7 @@ const Degree360Templates: React.FC = () => {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Sıra
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Sıra</label>
|
||||
<input
|
||||
type="number"
|
||||
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"
|
||||
/>
|
||||
<label
|
||||
htmlFor="isRequired"
|
||||
className="ml-2 block text-sm text-gray-700"
|
||||
>
|
||||
<label htmlFor="isRequired" className="ml-2 block text-sm text-gray-700">
|
||||
Zorunlu soru
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -855,8 +772,8 @@ const Degree360Templates: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default Degree360Templates;
|
||||
export default Degree360Templates
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
FaPlus,
|
||||
FaEdit,
|
||||
|
|
@ -9,53 +9,51 @@ import {
|
|||
FaEye,
|
||||
FaList,
|
||||
FaTh,
|
||||
} from "react-icons/fa";
|
||||
import { HrDepartment } from "../../../types/hr";
|
||||
import DataTable, { Column } from "../../../components/common/DataTable";
|
||||
import { mockDepartments } from "../../../mocks/mockDepartments";
|
||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||||
import { mockCostCenters } from "../../../mocks/mockCostCenters";
|
||||
import DepartmentFormModal from "./DepartmentFormModal";
|
||||
import DepartmentViewModal from "./DepartmentViewModal";
|
||||
import Widget from "../../../components/common/Widget";
|
||||
} from 'react-icons/fa'
|
||||
import { HrDepartment } from '../../../types/hr'
|
||||
import DataTable, { Column } from '../../../components/common/DataTable'
|
||||
import { mockDepartments } from '../../../mocks/mockDepartments'
|
||||
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||
import { mockCostCenters } from '../../../mocks/mockCostCenters'
|
||||
import DepartmentFormModal from './DepartmentFormModal'
|
||||
import DepartmentViewModal from './DepartmentViewModal'
|
||||
import Widget from '../../../components/common/Widget'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const DepartmentManagement: React.FC = () => {
|
||||
const [departments, setDepartments] =
|
||||
useState<HrDepartment[]>(mockDepartments);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedParent, setSelectedParent] = useState<string>("all");
|
||||
const [viewMode, setViewMode] = useState<"list" | "cards">("list");
|
||||
const [departments, setDepartments] = useState<HrDepartment[]>(mockDepartments)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedParent, setSelectedParent] = useState<string>('all')
|
||||
const [viewMode, setViewMode] = useState<'list' | 'cards'>('list')
|
||||
|
||||
// Modal states
|
||||
const [isFormModalOpen, setIsFormModalOpen] = useState(false);
|
||||
const [isViewModalOpen, setIsViewModalOpen] = useState(false);
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<
|
||||
HrDepartment | undefined
|
||||
>();
|
||||
const [modalTitle, setModalTitle] = useState("");
|
||||
const [isFormModalOpen, setIsFormModalOpen] = useState(false)
|
||||
const [isViewModalOpen, setIsViewModalOpen] = useState(false)
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<HrDepartment | undefined>()
|
||||
const [modalTitle, setModalTitle] = useState('')
|
||||
|
||||
const handleAdd = () => {
|
||||
setSelectedDepartment(undefined);
|
||||
setModalTitle("Yeni Departman");
|
||||
setIsFormModalOpen(true);
|
||||
};
|
||||
setSelectedDepartment(undefined)
|
||||
setModalTitle('Yeni Departman')
|
||||
setIsFormModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEdit = (department: HrDepartment) => {
|
||||
setSelectedDepartment(department);
|
||||
setModalTitle("Departman Düzenle");
|
||||
setIsFormModalOpen(true);
|
||||
};
|
||||
setSelectedDepartment(department)
|
||||
setModalTitle('Departman Düzenle')
|
||||
setIsFormModalOpen(true)
|
||||
}
|
||||
|
||||
const handleView = (department: HrDepartment) => {
|
||||
setSelectedDepartment(department);
|
||||
setIsViewModalOpen(true);
|
||||
};
|
||||
setSelectedDepartment(department)
|
||||
setIsViewModalOpen(true)
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if (confirm("Bu departmanı silmek istediğinizden emin misiniz?")) {
|
||||
setDepartments((prev) => prev.filter((dept) => dept.id !== id));
|
||||
if (confirm('Bu departmanı silmek istediğinizden emin misiniz?')) {
|
||||
setDepartments((prev) => prev.filter((dept) => dept.id !== id))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleSave = (departmentData: Partial<HrDepartment>) => {
|
||||
if (selectedDepartment) {
|
||||
|
|
@ -64,9 +62,9 @@ const DepartmentManagement: React.FC = () => {
|
|||
prev.map((dept) =>
|
||||
dept.id === selectedDepartment.id
|
||||
? { ...dept, ...departmentData, lastModificationTime: new Date() }
|
||||
: dept
|
||||
)
|
||||
);
|
||||
: dept,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// Add new department
|
||||
const newDepartment: HrDepartment = {
|
||||
|
|
@ -75,37 +73,35 @@ const DepartmentManagement: React.FC = () => {
|
|||
subDepartments: [],
|
||||
creationTime: new Date(),
|
||||
lastModificationTime: new Date(),
|
||||
} as HrDepartment;
|
||||
setDepartments((prev) => [...prev, newDepartment]);
|
||||
} as HrDepartment
|
||||
setDepartments((prev) => [...prev, newDepartment])
|
||||
}
|
||||
setIsFormModalOpen(false);
|
||||
};
|
||||
setIsFormModalOpen(false)
|
||||
}
|
||||
|
||||
const handleCloseFormModal = () => {
|
||||
setIsFormModalOpen(false);
|
||||
setSelectedDepartment(undefined);
|
||||
};
|
||||
setIsFormModalOpen(false)
|
||||
setSelectedDepartment(undefined)
|
||||
}
|
||||
|
||||
const handleCloseViewModal = () => {
|
||||
setIsViewModalOpen(false);
|
||||
setSelectedDepartment(undefined);
|
||||
};
|
||||
setIsViewModalOpen(false)
|
||||
setSelectedDepartment(undefined)
|
||||
}
|
||||
|
||||
const handleEditFromView = (department: HrDepartment) => {
|
||||
setIsViewModalOpen(false);
|
||||
handleEdit(department);
|
||||
};
|
||||
setIsViewModalOpen(false)
|
||||
handleEdit(department)
|
||||
}
|
||||
|
||||
mockDepartments.forEach((dept) => {
|
||||
if (dept.managerId) {
|
||||
dept.manager = mockEmployees.find((emp) => emp.id === dept.managerId);
|
||||
dept.manager = mockEmployees.find((emp) => emp.id === dept.managerId)
|
||||
}
|
||||
if (dept.costCenterId) {
|
||||
dept.costCenter = mockCostCenters.find(
|
||||
(cc) => cc.id === dept.costCenterId
|
||||
);
|
||||
dept.costCenter = mockCostCenters.find((cc) => cc.id === dept.costCenterId)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const filteredDepartments = departments.filter((department) => {
|
||||
if (
|
||||
|
|
@ -113,96 +109,81 @@ const DepartmentManagement: React.FC = () => {
|
|||
!department.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
|
||||
!department.code.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (
|
||||
selectedParent !== "all" &&
|
||||
department.parentDepartmentId !== selectedParent
|
||||
) {
|
||||
return false;
|
||||
if (selectedParent !== 'all' && department.parentDepartmentId !== selectedParent) {
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
const columns: Column<HrDepartment>[] = [
|
||||
{
|
||||
key: "code",
|
||||
header: "Departman Kodu",
|
||||
key: 'code',
|
||||
header: 'Departman Kodu',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
header: "Departman Adı",
|
||||
key: 'name',
|
||||
header: 'Departman Adı',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "parentDepartment",
|
||||
header: "Üst Departman",
|
||||
render: (department: HrDepartment) =>
|
||||
department.parentDepartment?.name || "-",
|
||||
key: 'parentDepartment',
|
||||
header: 'Üst Departman',
|
||||
render: (department: HrDepartment) => department.parentDepartment?.name || '-',
|
||||
},
|
||||
{
|
||||
key: "manager",
|
||||
header: "Yönetici",
|
||||
render: (department: HrDepartment) => department.manager?.fullName || "-",
|
||||
key: 'manager',
|
||||
header: 'Yönetici',
|
||||
render: (department: HrDepartment) => department.manager?.fullName || '-',
|
||||
},
|
||||
{
|
||||
key: "employeeCount",
|
||||
header: "Personel Sayısı",
|
||||
key: 'employeeCount',
|
||||
header: 'Personel Sayısı',
|
||||
render: (department: HrDepartment) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaUsers className="w-4 h-4 text-gray-500" />
|
||||
<span>
|
||||
{mockEmployees.filter((a) => a.departmantId == department.id)
|
||||
.length || 0}
|
||||
</span>
|
||||
<span>{mockEmployees.filter((a) => a.departmantId == department.id).length || 0}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "costCenter",
|
||||
header: "Maliyet Merkezi",
|
||||
key: 'costCenter',
|
||||
header: 'Maliyet Merkezi',
|
||||
render: (department: HrDepartment) => (
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
{department.costCenter?.code || "-"}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{department.costCenter?.name || "-"}
|
||||
</span>
|
||||
<span className="font-medium">{department.costCenter?.code || '-'}</span>
|
||||
<span className="text-xs text-gray-500">{department.costCenter?.name || '-'}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "budget",
|
||||
header: "Bütçe",
|
||||
key: 'budget',
|
||||
header: 'Bütçe',
|
||||
render: (department: HrDepartment) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaDollarSign className="w-4 h-4 text-gray-500" />
|
||||
<span>
|
||||
{department.budget ? `₺${department.budget.toLocaleString()}` : "-"}
|
||||
</span>
|
||||
<span>{department.budget ? `₺${department.budget.toLocaleString()}` : '-'}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
header: "Durum",
|
||||
key: 'status',
|
||||
header: 'Durum',
|
||||
render: (department: HrDepartment) => (
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
department.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
department.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{department.isActive ? "Aktif" : "Pasif"}
|
||||
{department.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
header: "İşlemler",
|
||||
key: 'actions',
|
||||
header: 'İşlemler',
|
||||
render: (department: HrDepartment) => (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
|
|
@ -229,246 +210,234 @@ const DepartmentManagement: React.FC = () => {
|
|||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-gray-900">
|
||||
Departman Yönetimi
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Organizasyon yapısını ve departmanları yönetin
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-0.5">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-gray-900">Departman Yönetimi</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Organizasyon yapısını ve departmanları yönetin
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-0.5">
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'list'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('cards')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'cards'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === "list"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-600 hover:text-gray-900"
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
onClick={handleAdd}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("cards")}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === "cards"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-600 hover:text-gray-900"
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Departman
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Departman
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<Widget
|
||||
title="Toplam Departman"
|
||||
value={departments.length}
|
||||
color="blue"
|
||||
icon="FaBuilding"
|
||||
/>
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<Widget
|
||||
title="Toplam Departman"
|
||||
value={departments.length}
|
||||
color="blue"
|
||||
icon="FaBuilding"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Toplam Personel"
|
||||
value={mockEmployees.length}
|
||||
color="green"
|
||||
icon="FaUsers"
|
||||
/>
|
||||
<Widget
|
||||
title="Toplam Personel"
|
||||
value={mockEmployees.length}
|
||||
color="green"
|
||||
icon="FaUsers"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Ana Departmanlar"
|
||||
value={departments.filter((d) => !d.parentDepartmentId).length}
|
||||
color="purple"
|
||||
icon="FaBuilding"
|
||||
/>
|
||||
<Widget
|
||||
title="Ana Departmanlar"
|
||||
value={departments.filter((d) => !d.parentDepartmentId).length}
|
||||
color="purple"
|
||||
icon="FaBuilding"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Toplam Bütçe"
|
||||
value={`₺${departments
|
||||
.reduce((total, dept) => total + (dept.budget || 0), 0)
|
||||
.toLocaleString()}`}
|
||||
color="orange"
|
||||
icon="FaDollarSign"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Departman adı veya kodu ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
<Widget
|
||||
title="Toplam Bütçe"
|
||||
value={`₺${departments
|
||||
.reduce((total, dept) => total + (dept.budget || 0), 0)
|
||||
.toLocaleString()}`}
|
||||
color="orange"
|
||||
icon="FaDollarSign"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<select
|
||||
value={selectedParent}
|
||||
onChange={(e) => setSelectedParent(e.target.value)}
|
||||
className="px-3 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[200px]"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{departments
|
||||
.filter((d) => !d.parentDepartmentId)
|
||||
.map((dept) => (
|
||||
<option key={dept.id} value={dept.id}>
|
||||
{dept.name} Alt Departmanları
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Departman adı veya kodu ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Data Display */}
|
||||
{viewMode === "list" ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border overflow-x-auto">
|
||||
<DataTable data={filteredDepartments} columns={columns} />
|
||||
<select
|
||||
value={selectedParent}
|
||||
onChange={(e) => setSelectedParent(e.target.value)}
|
||||
className="px-3 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[200px]"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{departments
|
||||
.filter((d) => !d.parentDepartmentId)
|
||||
.map((dept) => (
|
||||
<option key={dept.id} value={dept.id}>
|
||||
{dept.name} Alt Departmanları
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
{filteredDepartments.map((department) => (
|
||||
<div
|
||||
key={department.id}
|
||||
className="bg-white rounded-lg shadow-sm border hover:shadow-md transition-shadow p-3"
|
||||
>
|
||||
{/* Card Header */}
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className={`w-12 h-12 rounded-lg flex items-center justify-center ${
|
||||
|
||||
{/* Data Display */}
|
||||
{viewMode === 'list' ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border overflow-x-auto">
|
||||
<DataTable data={filteredDepartments} columns={columns} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
{filteredDepartments.map((department) => (
|
||||
<div
|
||||
key={department.id}
|
||||
className="bg-white rounded-lg shadow-sm border hover:shadow-md transition-shadow p-3"
|
||||
>
|
||||
{/* Card Header */}
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className={`w-12 h-12 rounded-lg flex items-center justify-center ${
|
||||
department.isActive
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: 'bg-gray-100 text-gray-500'
|
||||
}`}
|
||||
>
|
||||
<FaBuilding className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm text-gray-900 truncate">
|
||||
{department.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">{department.code}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
department.isActive
|
||||
? "bg-blue-100 text-blue-600"
|
||||
: "bg-gray-100 text-gray-500"
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
<FaBuilding className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm text-gray-900 truncate">
|
||||
{department.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">{department.code}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
department.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
}`}
|
||||
>
|
||||
{department.isActive ? "Aktif" : "Pasif"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Card Content */}
|
||||
<div className="space-y-2">
|
||||
{department.parentDepartment && (
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaBuilding className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>Üst: {department.parentDepartment.name}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{department.manager && (
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaUsers className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>Yönetici: {department.manager.fullName}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaUsers className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>
|
||||
{
|
||||
mockEmployees.filter(
|
||||
(emp) => emp.departmantId === department.id
|
||||
).length
|
||||
}{" "}
|
||||
Personel
|
||||
{department.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{department.budget && (
|
||||
{/* Card Content */}
|
||||
<div className="space-y-2">
|
||||
{department.parentDepartment && (
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaBuilding className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>Üst: {department.parentDepartment.name}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{department.manager && (
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaUsers className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>Yönetici: {department.manager.fullName}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaDollarSign className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>₺{department.budget.toLocaleString()}</span>
|
||||
<FaUsers className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>
|
||||
{mockEmployees.filter((emp) => emp.departmantId === department.id).length}{' '}
|
||||
Personel
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{department.costCenter && (
|
||||
<div className="text-xs text-gray-600">
|
||||
<div className="font-medium">
|
||||
{department.costCenter.code}
|
||||
{department.budget && (
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaDollarSign className="w-4 h-4 mr-2 text-gray-400" />
|
||||
<span>₺{department.budget.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{department.costCenter.name}
|
||||
)}
|
||||
|
||||
{department.costCenter && (
|
||||
<div className="text-xs text-gray-600">
|
||||
<div className="font-medium">{department.costCenter.code}</div>
|
||||
<div className="text-xs text-gray-500">{department.costCenter.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Card Actions */}
|
||||
<div className="flex justify-end space-x-1 mt-3 pt-3 border-t border-gray-100">
|
||||
<button
|
||||
onClick={() => handleView(department)}
|
||||
className="p-1.5 text-green-600 hover:bg-green-50 rounded-lg transition-colors"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEdit(department)}
|
||||
className="p-1.5 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(department.id)}
|
||||
className="p-1.5 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Sil"
|
||||
>
|
||||
<FaTrash className="w-4 h-4" />
|
||||
</button>
|
||||
{/* Card Actions */}
|
||||
<div className="flex justify-end space-x-1 mt-3 pt-3 border-t border-gray-100">
|
||||
<button
|
||||
onClick={() => handleView(department)}
|
||||
className="p-1.5 text-green-600 hover:bg-green-50 rounded-lg transition-colors"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEdit(department)}
|
||||
className="p-1.5 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(department.id)}
|
||||
className="p-1.5 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Sil"
|
||||
>
|
||||
<FaTrash className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredDepartments.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaBuilding className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
Departman bulunamadı
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
Arama kriterlerinizi değiştirmeyi deneyin.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{filteredDepartments.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaBuilding className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Departman bulunamadı</h3>
|
||||
<p className="text-gray-500">Arama kriterlerinizi değiştirmeyi deneyin.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<DepartmentFormModal
|
||||
|
|
@ -485,8 +454,8 @@ const DepartmentManagement: React.FC = () => {
|
|||
department={selectedDepartment || null}
|
||||
onEdit={handleEditFromView}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default DepartmentManagement;
|
||||
export default DepartmentManagement
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
FaUser,
|
||||
FaEnvelope,
|
||||
|
|
@ -9,194 +9,174 @@ import {
|
|||
FaEye,
|
||||
FaTrash,
|
||||
FaPlus,
|
||||
} from "react-icons/fa";
|
||||
import { HrEmployee, EmployeeStatusEnum } from "../../../types/hr";
|
||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||||
import {
|
||||
getEmployeeStatusColor,
|
||||
getEmployeeStatusText,
|
||||
} from "../../../utils/erp";
|
||||
} from 'react-icons/fa'
|
||||
import { HrEmployee, EmployeeStatusEnum } from '../../../types/hr'
|
||||
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||
import { getEmployeeStatusColor, getEmployeeStatusText } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const EmployeeCards: React.FC = () => {
|
||||
const [employees] = useState<HrEmployee[]>(mockEmployees);
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<string>("all");
|
||||
const [selectedStatus, setSelectedStatus] = useState<string>("all");
|
||||
const [employees] = useState<HrEmployee[]>(mockEmployees)
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
|
||||
const [selectedStatus, setSelectedStatus] = useState<string>('all')
|
||||
|
||||
const handleEdit = (employee: HrEmployee) => {
|
||||
console.log("Edit employee:", employee);
|
||||
console.log('Edit employee:', employee)
|
||||
// Implement edit functionality
|
||||
};
|
||||
}
|
||||
|
||||
const handleView = (employee: HrEmployee) => {
|
||||
console.log("View employee:", employee);
|
||||
console.log('View employee:', employee)
|
||||
// Implement view functionality
|
||||
};
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
console.log("Delete employee:", id);
|
||||
console.log('Delete employee:', id)
|
||||
// Implement delete functionality
|
||||
};
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
console.log("Add new employee");
|
||||
console.log('Add new employee')
|
||||
// Implement add functionality
|
||||
};
|
||||
}
|
||||
|
||||
const filteredEmployees = employees.filter((employee) => {
|
||||
if (
|
||||
selectedDepartment !== "all" &&
|
||||
employee.department?.id !== selectedDepartment
|
||||
) {
|
||||
return false;
|
||||
if (selectedDepartment !== 'all' && employee.department?.id !== selectedDepartment) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
selectedStatus !== "all" &&
|
||||
employee.employeeStatus !== selectedStatus
|
||||
) {
|
||||
return false;
|
||||
if (selectedStatus !== 'all' && employee.employeeStatus !== selectedStatus) {
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
{/* Header with Add Button */}
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-bold text-gray-900">Personel Kartları</h2>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="px-2 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors flex items-center gap-2"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Personel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex gap-3 mb-4">
|
||||
<select
|
||||
value={selectedDepartment}
|
||||
onChange={(e) => setSelectedDepartment(e.target.value)}
|
||||
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{/* Department options would be populated from departments list */}
|
||||
</select>
|
||||
|
||||
<select
|
||||
value={selectedStatus}
|
||||
onChange={(e) => setSelectedStatus(e.target.value)}
|
||||
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="all">Tüm Durumlar</option>
|
||||
<option value={EmployeeStatusEnum.Active}>Aktif</option>
|
||||
<option value={EmployeeStatusEnum.Inactive}>Pasif</option>
|
||||
<option value={EmployeeStatusEnum.OnLeave}>İzinli</option>
|
||||
<option value={EmployeeStatusEnum.Suspended}>Askıda</option>
|
||||
<option value={EmployeeStatusEnum.Terminated}>
|
||||
İşten Çıkarılmış
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Employee Cards Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filteredEmployees.map((employee) => (
|
||||
<div
|
||||
key={employee.id}
|
||||
className="bg-white rounded-lg shadow-sm p-3 hover:shadow-md transition-shadow border"
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header with Add Button */}
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-bold text-gray-900">Personel Kartları</h2>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="px-2 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors flex items-center gap-2"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<FaUser className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${getEmployeeStatusColor(
|
||||
employee.employeeStatus
|
||||
)}`}
|
||||
>
|
||||
{getEmployeeStatusText(employee.employeeStatus)}
|
||||
</span>
|
||||
</div>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
Yeni Personel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Employee Info */}
|
||||
<div className="space-y-1 mb-3">
|
||||
<h3 className="font-semibold text-sm text-gray-900">
|
||||
{employee.fullName}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-600">
|
||||
{employee.jobPosition?.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{employee.department?.name}
|
||||
</p>
|
||||
</div>
|
||||
{/* Filters */}
|
||||
<div className="flex gap-3 mb-4">
|
||||
<select
|
||||
value={selectedDepartment}
|
||||
onChange={(e) => setSelectedDepartment(e.target.value)}
|
||||
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{/* Department options would be populated from departments list */}
|
||||
</select>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="space-y-1.5 mb-3">
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaEnvelope className="w-4 h-4 mr-2" />
|
||||
<span className="truncate">{employee.email}</span>
|
||||
</div>
|
||||
{employee.phone && (
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaPhone className="w-4 h-4 mr-2" />
|
||||
<span>{employee.phone}</span>
|
||||
<select
|
||||
value={selectedStatus}
|
||||
onChange={(e) => setSelectedStatus(e.target.value)}
|
||||
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="all">Tüm Durumlar</option>
|
||||
<option value={EmployeeStatusEnum.Active}>Aktif</option>
|
||||
<option value={EmployeeStatusEnum.Inactive}>Pasif</option>
|
||||
<option value={EmployeeStatusEnum.OnLeave}>İzinli</option>
|
||||
<option value={EmployeeStatusEnum.Suspended}>Askıda</option>
|
||||
<option value={EmployeeStatusEnum.Terminated}>İşten Çıkarılmış</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Employee Cards Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filteredEmployees.map((employee) => (
|
||||
<div
|
||||
key={employee.id}
|
||||
className="bg-white rounded-lg shadow-sm p-3 hover:shadow-md transition-shadow border"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<FaUser className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaCalendar className="w-4 h-4 mr-2" />
|
||||
<span>
|
||||
{new Date(employee.hireDate).toLocaleDateString("tr-TR")}
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${getEmployeeStatusColor(
|
||||
employee.employeeStatus,
|
||||
)}`}
|
||||
>
|
||||
{getEmployeeStatusText(employee.employeeStatus)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaCertificate className="w-4 h-4 mr-2" />
|
||||
<span>{employee.code}</span>
|
||||
|
||||
{/* Employee Info */}
|
||||
<div className="space-y-1 mb-3">
|
||||
<h3 className="font-semibold text-sm text-gray-900">{employee.fullName}</h3>
|
||||
<p className="text-xs text-gray-600">{employee.jobPosition?.name}</p>
|
||||
<p className="text-sm text-gray-500">{employee.department?.name}</p>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="space-y-1.5 mb-3">
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaEnvelope className="w-4 h-4 mr-2" />
|
||||
<span className="truncate">{employee.email}</span>
|
||||
</div>
|
||||
{employee.phone && (
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaPhone className="w-4 h-4 mr-2" />
|
||||
<span>{employee.phone}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaCalendar className="w-4 h-4 mr-2" />
|
||||
<span>{new Date(employee.hireDate).toLocaleDateString('tr-TR')}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-xs text-gray-600">
|
||||
<FaCertificate className="w-4 h-4 mr-2" />
|
||||
<span>{employee.code}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 pt-3 border-t">
|
||||
<button
|
||||
onClick={() => handleView(employee)}
|
||||
className="flex-1 px-2 py-1 text-xs bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors flex items-center justify-center gap-1"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
Görüntüle
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEdit(employee)}
|
||||
className="flex-1 px-2 py-1 text-xs bg-green-50 text-green-600 rounded-md hover:bg-green-100 transition-colors flex items-center justify-center gap-1"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
Düzenle
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(employee.id)}
|
||||
className="px-2 py-1 text-xs bg-red-50 text-red-600 rounded-md hover:bg-red-100 transition-colors flex items-center justify-center"
|
||||
>
|
||||
<FaTrash className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 pt-3 border-t">
|
||||
<button
|
||||
onClick={() => handleView(employee)}
|
||||
className="flex-1 px-2 py-1 text-xs bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors flex items-center justify-center gap-1"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
Görüntüle
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEdit(employee)}
|
||||
className="flex-1 px-2 py-1 text-xs bg-green-50 text-green-600 rounded-md hover:bg-green-100 transition-colors flex items-center justify-center gap-1"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
Düzenle
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(employee.id)}
|
||||
className="px-2 py-1 text-xs bg-red-50 text-red-600 rounded-md hover:bg-red-100 transition-colors flex items-center justify-center"
|
||||
>
|
||||
<FaTrash className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredEmployees.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaUser className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
||||
Personel bulunamadı
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
Seçilen kriterlere uygun personel bulunmamaktadır.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmployeeCards;
|
||||
{filteredEmployees.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaUser className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">Personel bulunamadı</h3>
|
||||
<p className="text-gray-500">Seçilen kriterlere uygun personel bulunmamaktadır.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmployeeCards
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,77 +1,497 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
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 { mockEmployees } from "../../../mocks/mockEmployees";
|
||||
import EmployeeViewModal from "./EmployeeViewModal";
|
||||
import LoadingSpinner from "../../../components/common/LoadingSpinner";
|
||||
import {
|
||||
getEmployeeStatusColor,
|
||||
getEmployeeStatusIcon,
|
||||
getEmployeeStatusText,
|
||||
} from "../../../utils/erp";
|
||||
|
||||
const EmployeeView: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [employee, setEmployee] = useState<HrEmployee | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
interface EmployeeViewModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
employee: HrEmployee | null;
|
||||
onEdit?: (employee: HrEmployee) => void;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const loadEmployee = async () => {
|
||||
setLoading(true);
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
const EmployeeViewModal: React.FC<EmployeeViewModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
employee,
|
||||
onEdit,
|
||||
}) => {
|
||||
if (!isOpen || !employee) return null;
|
||||
|
||||
const foundEmployee = mockEmployees.find((emp) => emp.id === id);
|
||||
setEmployee(foundEmployee || null);
|
||||
setLoading(false);
|
||||
};
|
||||
const formatDate = (date: Date | string): string => {
|
||||
return new Date(date).toLocaleDateString("tr-TR");
|
||||
};
|
||||
|
||||
if (id) {
|
||||
loadEmployee();
|
||||
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`;
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const handleClose = () => {
|
||||
navigate("/admin/hr/employees");
|
||||
return `${months} ay`;
|
||||
};
|
||||
|
||||
const handleEdit = (employee: HrEmployee) => {
|
||||
navigate(`/admin/hr/employees/edit/${employee.id}`);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!employee) {
|
||||
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>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="px-3 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors text-sm"
|
||||
>
|
||||
Personel Listesine Dön
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EmployeeViewModal
|
||||
isOpen={true}
|
||||
onClose={handleClose}
|
||||
employee={employee}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
<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 EmployeeView;
|
||||
export default EmployeeViewModal;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,324 +1,305 @@
|
|||
import React, { useState } from "react";
|
||||
import { FaPlus, FaEdit, FaEye, FaTh, FaList, FaSearch } from "react-icons/fa";
|
||||
import { EmploymentTypeEnum } from "../../../types/hr";
|
||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||||
import { mockEmployeeTypes } from "../../../mocks/mockEmployeeTypes";
|
||||
import Widget from "../../../components/common/Widget";
|
||||
import {
|
||||
getEmploymentTypeColor,
|
||||
getEmploymentTypeText,
|
||||
} from "../../../utils/erp";
|
||||
import React, { useState } from 'react'
|
||||
import { FaPlus, FaEdit, FaEye, FaTh, FaList, FaSearch } from 'react-icons/fa'
|
||||
import { EmploymentTypeEnum } from '../../../types/hr'
|
||||
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||
import { mockEmployeeTypes } from '../../../mocks/mockEmployeeTypes'
|
||||
import Widget from '../../../components/common/Widget'
|
||||
import { getEmploymentTypeColor, getEmploymentTypeText } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const EmploymentTypes: React.FC = () => {
|
||||
const [viewMode, setViewMode] = useState<"card" | "list">("list");
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedEmploymentType, setSelectedEmploymentType] =
|
||||
useState<EmploymentTypeEnum | null>(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [modalMode, setModalMode] = useState<"new" | "edit" | "view">("view");
|
||||
const [viewMode, setViewMode] = useState<'card' | 'list'>('list')
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedEmploymentType, setSelectedEmploymentType] = useState<EmploymentTypeEnum | null>(
|
||||
null,
|
||||
)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [modalMode, setModalMode] = useState<'new' | 'edit' | 'view'>('view')
|
||||
|
||||
const handleNewEmploymentType = () => {
|
||||
setModalMode("new");
|
||||
setSelectedEmploymentType(null);
|
||||
setShowModal(true);
|
||||
};
|
||||
setModalMode('new')
|
||||
setSelectedEmploymentType(null)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const handleEditEmploymentType = (type: EmploymentTypeEnum) => {
|
||||
setModalMode("edit");
|
||||
setSelectedEmploymentType(type);
|
||||
setShowModal(true);
|
||||
};
|
||||
setModalMode('edit')
|
||||
setSelectedEmploymentType(type)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const handleViewEmploymentType = (type: EmploymentTypeEnum) => {
|
||||
setModalMode("view");
|
||||
setSelectedEmploymentType(type);
|
||||
setShowModal(true);
|
||||
};
|
||||
setModalMode('view')
|
||||
setSelectedEmploymentType(type)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
// Filter employee types based on search term
|
||||
const filteredEmployeeTypes = mockEmployeeTypes.filter((employeeType) =>
|
||||
getEmploymentTypeText(employeeType.name as EmploymentTypeEnum)
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase())
|
||||
);
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Header */}
|
||||
<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>
|
||||
<h2 className="text-xl font-bold text-gray-900">
|
||||
İstihdam Türleri
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Personel istihdam türleri ve dağılımı
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* New Button - Visible on larger screens */}
|
||||
<div className="hidden sm:flex gap-2">
|
||||
<button
|
||||
onClick={handleNewEmploymentType}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
<span>Yeni</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search and Controls */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
|
||||
{/* Search */}
|
||||
<div className="relative flex-1">
|
||||
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="İstihdam türü ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 pr-4 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex justify-between sm:justify-end items-center gap-3">
|
||||
{/* View Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setViewMode("card")}
|
||||
className={`p-2 rounded ${
|
||||
viewMode === "card"
|
||||
? "bg-white shadow text-blue-600"
|
||||
: "text-gray-600 hover:text-gray-800"
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
className={`p-2 rounded ${
|
||||
viewMode === "list"
|
||||
? "bg-white shadow text-blue-600"
|
||||
: "text-gray-600 hover:text-gray-800"
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<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>
|
||||
<h2 className="text-xl font-bold text-gray-900">İstihdam Türleri</h2>
|
||||
<p className="text-gray-600 mt-1">Personel istihdam türleri ve dağılımı</p>
|
||||
</div>
|
||||
|
||||
{/* New Button - Visible on mobile */}
|
||||
<div className="sm:hidden">
|
||||
{/* New Button - Visible on larger screens */}
|
||||
<div className="hidden sm:flex gap-2">
|
||||
<button
|
||||
onClick={handleNewEmploymentType}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
<span className="text-sm">Yeni</span>
|
||||
<span>Yeni</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overview Stats */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Widget
|
||||
title="Toplam Personel"
|
||||
value={mockEmployees.length}
|
||||
color="blue"
|
||||
icon="FaUsers"
|
||||
/>
|
||||
{/* Search and Controls */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
|
||||
{/* Search */}
|
||||
<div className="relative flex-1">
|
||||
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="İstihdam türü ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 pr-4 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Widget
|
||||
title="Tam Zamanlı"
|
||||
value={
|
||||
mockEmployees.filter(
|
||||
(a) => a.employmentType === EmploymentTypeEnum.FullTime
|
||||
).length || 0
|
||||
}
|
||||
color="green"
|
||||
icon="FaClock"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Sözleşmeli"
|
||||
value={
|
||||
mockEmployees.filter(
|
||||
(a) => a.employmentType === EmploymentTypeEnum.Contract
|
||||
).length || 0
|
||||
}
|
||||
color="orange"
|
||||
icon="FaFileAlt"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="İstihdam Türü"
|
||||
value={mockEmployeeTypes.length}
|
||||
color="purple"
|
||||
icon="FaChartBar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Employment Type Distribution */}
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
{viewMode === "card" ? (
|
||||
// Card View
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
||||
{filteredEmployeeTypes.map((a) => {
|
||||
const type = a.name as EmploymentTypeEnum; // veya a’dan türet
|
||||
const count = a.count || 0;
|
||||
const total = mockEmployees.length;
|
||||
const percentage = total > 0 ? (count / total) * 100 : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={a.id}
|
||||
className="border rounded-lg p-3 hover:shadow-md transition-shadow"
|
||||
{/* Controls */}
|
||||
<div className="flex justify-between sm:justify-end items-center gap-3">
|
||||
{/* View Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setViewMode('card')}
|
||||
className={`p-2 rounded ${
|
||||
viewMode === 'card'
|
||||
? 'bg-white shadow text-blue-600'
|
||||
: 'text-gray-600 hover:text-gray-800'
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full ${getEmploymentTypeColor(
|
||||
type
|
||||
)}`}
|
||||
/>
|
||||
<h4 className="font-medium text-gray-900 text-sm">
|
||||
{getEmploymentTypeText(type)}
|
||||
</h4>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">
|
||||
{percentage.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-2 rounded ${
|
||||
viewMode === 'list'
|
||||
? 'bg-white shadow text-blue-600'
|
||||
: 'text-gray-600 hover:text-gray-800'
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">
|
||||
Personel Sayısı
|
||||
</span>
|
||||
<span className="text-xs font-medium">{count}</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getEmploymentTypeColor(
|
||||
type
|
||||
)}`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-2 mt-2 pt-2 border-t">
|
||||
<button
|
||||
onClick={() => handleViewEmploymentType(type)}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-3 h-3" />
|
||||
<span className="hidden sm:inline">Görüntüle</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditEmploymentType(type)}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs bg-yellow-50 text-yellow-600 rounded-md hover:bg-yellow-100 transition-colors"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-3 h-3" />
|
||||
<span className="hidden sm:inline">Düzenle</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* New Button - Visible on mobile */}
|
||||
<div className="sm:hidden">
|
||||
<button
|
||||
onClick={handleNewEmploymentType}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
<span className="text-sm">Yeni</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// List View
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr className="text-sm">
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
İstihdam Türü
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Personel Sayısı
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Yüzde
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
İşlemler
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredEmployeeTypes.map((a) => {
|
||||
const type = a.name as EmploymentTypeEnum;
|
||||
const count = a.count || 0;
|
||||
const total = mockEmployees.length;
|
||||
const percentage = total > 0 ? (count / total) * 100 : 0;
|
||||
return (
|
||||
<tr key={type} className="hover:bg-gray-50">
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full mr-3 ${getEmploymentTypeColor(
|
||||
type
|
||||
)}`}
|
||||
/>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{getEmploymentTypeText(type)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{count}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<div className="w-16 bg-gray-200 rounded-full h-2 mr-2">
|
||||
</div>
|
||||
|
||||
{/* Overview Stats */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Widget
|
||||
title="Toplam Personel"
|
||||
value={mockEmployees.length}
|
||||
color="blue"
|
||||
icon="FaUsers"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Tam Zamanlı"
|
||||
value={
|
||||
mockEmployees.filter((a) => a.employmentType === EmploymentTypeEnum.FullTime)
|
||||
.length || 0
|
||||
}
|
||||
color="green"
|
||||
icon="FaClock"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Sözleşmeli"
|
||||
value={
|
||||
mockEmployees.filter((a) => a.employmentType === EmploymentTypeEnum.Contract)
|
||||
.length || 0
|
||||
}
|
||||
color="orange"
|
||||
icon="FaFileAlt"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="İstihdam Türü"
|
||||
value={mockEmployeeTypes.length}
|
||||
color="purple"
|
||||
icon="FaChartBar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Employment Type Distribution */}
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
{viewMode === 'card' ? (
|
||||
// Card View
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
||||
{filteredEmployeeTypes.map((a) => {
|
||||
const type = a.name as EmploymentTypeEnum // veya a’dan türet
|
||||
const count = a.count || 0
|
||||
const total = mockEmployees.length
|
||||
const percentage = total > 0 ? (count / total) * 100 : 0
|
||||
|
||||
return (
|
||||
<div
|
||||
key={a.id}
|
||||
className="border rounded-lg p-3 hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-4 h-4 rounded-full ${getEmploymentTypeColor(type)}`} />
|
||||
<h4 className="font-medium text-gray-900 text-sm">
|
||||
{getEmploymentTypeText(type)}
|
||||
</h4>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">{percentage.toFixed(1)}%</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">Personel Sayısı</span>
|
||||
<span className="text-xs font-medium">{count}</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getEmploymentTypeColor(type)}`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-2 mt-2 pt-2 border-t">
|
||||
<button
|
||||
onClick={() => handleViewEmploymentType(type)}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-3 h-3" />
|
||||
<span className="hidden sm:inline">Görüntüle</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditEmploymentType(type)}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs bg-yellow-50 text-yellow-600 rounded-md hover:bg-yellow-100 transition-colors"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-3 h-3" />
|
||||
<span className="hidden sm:inline">Düzenle</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
// List View
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr className="text-sm">
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
İstihdam Türü
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Personel Sayısı
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Yüzde
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
İşlemler
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredEmployeeTypes.map((a) => {
|
||||
const type = a.name as EmploymentTypeEnum
|
||||
const count = a.count || 0
|
||||
const total = mockEmployees.length
|
||||
const percentage = total > 0 ? (count / total) * 100 : 0
|
||||
return (
|
||||
<tr key={type} className="hover:bg-gray-50">
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getEmploymentTypeColor(
|
||||
type
|
||||
className={`w-4 h-4 rounded-full mr-3 ${getEmploymentTypeColor(
|
||||
type,
|
||||
)}`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{getEmploymentTypeText(type)}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-gray-900">
|
||||
{percentage.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => handleViewEmploymentType(type)}
|
||||
className="text-blue-600 hover:text-blue-900 p-1"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditEmploymentType(type)}
|
||||
className="text-yellow-600 hover:text-yellow-900 p-1"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{count}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<div className="w-16 bg-gray-200 rounded-full h-2 mr-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getEmploymentTypeColor(type)}`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-gray-900">{percentage.toFixed(1)}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => handleViewEmploymentType(type)}
|
||||
className="text-blue-600 hover:text-blue-900 p-1"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditEmploymentType(type)}
|
||||
className="text-yellow-600 hover:text-yellow-900 p-1"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
|
|
@ -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="flex justify-between items-center mb-4">
|
||||
<h3 className="text-base font-semibold">
|
||||
{modalMode === "new" && "Yeni İstihdam Türü"}
|
||||
{modalMode === "edit" && "İstihdam Türünü Düzenle"}
|
||||
{modalMode === "view" && "İstihdam Türü Detayları"}
|
||||
{modalMode === 'new' && 'Yeni İstihdam Türü'}
|
||||
{modalMode === 'edit' && 'İstihdam Türünü Düzenle'}
|
||||
{modalMode === 'view' && 'İstihdam Türü Detayları'}
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
|
|
@ -340,7 +321,7 @@ const EmploymentTypes: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{modalMode === "new" && (
|
||||
{modalMode === 'new' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
|
|
@ -353,9 +334,7 @@ const EmploymentTypes: React.FC = () => {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Açıklama
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Açıklama</label>
|
||||
<textarea
|
||||
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"
|
||||
|
|
@ -365,7 +344,7 @@ const EmploymentTypes: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{modalMode === "edit" && selectedEmploymentType && (
|
||||
{modalMode === 'edit' && selectedEmploymentType && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
|
|
@ -373,16 +352,12 @@ const EmploymentTypes: React.FC = () => {
|
|||
</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={getEmploymentTypeText(
|
||||
selectedEmploymentType
|
||||
)}
|
||||
defaultValue={getEmploymentTypeText(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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Açıklama
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Açıklama</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
defaultValue="Bu istihdam türüne ait açıklama..."
|
||||
|
|
@ -392,7 +367,7 @@ const EmploymentTypes: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{modalMode === "view" && selectedEmploymentType && (
|
||||
{modalMode === 'view' && selectedEmploymentType && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">İstihdam Türü:</p>
|
||||
|
|
@ -401,15 +376,12 @@ const EmploymentTypes: React.FC = () => {
|
|||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">
|
||||
Toplam Personel Sayısı:
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">Toplam Personel Sayısı:</p>
|
||||
<p className="font-medium">
|
||||
{
|
||||
mockEmployees.filter(
|
||||
(emp) => emp.employmentType === selectedEmploymentType
|
||||
).length
|
||||
}{" "}
|
||||
mockEmployees.filter((emp) => emp.employmentType === selectedEmploymentType)
|
||||
.length
|
||||
}{' '}
|
||||
kişi
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -418,7 +390,7 @@ const EmploymentTypes: React.FC = () => {
|
|||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full ${getEmploymentTypeColor(
|
||||
selectedEmploymentType
|
||||
selectedEmploymentType,
|
||||
)}`}
|
||||
/>
|
||||
<span className="text-sm">
|
||||
|
|
@ -427,14 +399,10 @@ const EmploymentTypes: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 mb-2">
|
||||
Bu istihdam türündeki personeller:
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 mb-2">Bu istihdam türündeki personeller:</p>
|
||||
<div className="max-h-32 overflow-y-auto bg-gray-50 rounded p-2">
|
||||
{mockEmployees
|
||||
.filter(
|
||||
(emp) => emp.employmentType === selectedEmploymentType
|
||||
)
|
||||
.filter((emp) => emp.employmentType === selectedEmploymentType)
|
||||
.slice(0, 10)
|
||||
.map((emp) => (
|
||||
<div
|
||||
|
|
@ -444,15 +412,13 @@ const EmploymentTypes: React.FC = () => {
|
|||
{emp.fullName} - {emp.department?.name}
|
||||
</div>
|
||||
))}
|
||||
{mockEmployees.filter(
|
||||
(emp) => emp.employmentType === selectedEmploymentType
|
||||
).length > 10 && (
|
||||
{mockEmployees.filter((emp) => emp.employmentType === selectedEmploymentType)
|
||||
.length > 10 && (
|
||||
<div className="text-xs text-gray-500 pt-2">
|
||||
+
|
||||
{mockEmployees.filter(
|
||||
(emp) =>
|
||||
emp.employmentType === selectedEmploymentType
|
||||
).length - 10}{" "}
|
||||
(emp) => emp.employmentType === selectedEmploymentType,
|
||||
).length - 10}{' '}
|
||||
kişi daha...
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -467,22 +433,22 @@ const EmploymentTypes: React.FC = () => {
|
|||
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"
|
||||
>
|
||||
{modalMode === "view" ? "Kapat" : "İptal"}
|
||||
{modalMode === 'view' ? 'Kapat' : 'İptal'}
|
||||
</button>
|
||||
{modalMode !== "view" && (
|
||||
{modalMode !== 'view' && (
|
||||
<button
|
||||
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"
|
||||
>
|
||||
{modalMode === "new" ? "Oluştur" : "Güncelle"}
|
||||
{modalMode === 'new' ? 'Oluştur' : 'Güncelle'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmploymentTypes;
|
||||
export default EmploymentTypes
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
FaBriefcase,
|
||||
FaPlus,
|
||||
|
|
@ -10,51 +10,50 @@ import {
|
|||
FaEye,
|
||||
FaTh,
|
||||
FaList,
|
||||
} from "react-icons/fa";
|
||||
import { HrJobPosition, JobLevelEnum } from "../../../types/hr";
|
||||
import DataTable, { Column } from "../../../components/common/DataTable";
|
||||
import { mockJobPositions } from "../../../mocks/mockJobPositions";
|
||||
import JobPositionFormModal from "./JobPositionFormModal";
|
||||
import JobPositionViewModal from "./JobPositionViewModal";
|
||||
import { getJobLevelColor, getJobLevelText } from "../../../utils/erp";
|
||||
} from 'react-icons/fa'
|
||||
import { HrJobPosition, JobLevelEnum } from '../../../types/hr'
|
||||
import DataTable, { Column } from '../../../components/common/DataTable'
|
||||
import { mockJobPositions } from '../../../mocks/mockJobPositions'
|
||||
import JobPositionFormModal from './JobPositionFormModal'
|
||||
import JobPositionViewModal from './JobPositionViewModal'
|
||||
import { getJobLevelColor, getJobLevelText } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const JobPositions: React.FC = () => {
|
||||
const [positions, setPositions] = useState<HrJobPosition[]>(mockJobPositions);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedLevel, setSelectedLevel] = useState<string>("all");
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<string>("all");
|
||||
const [viewMode, setViewMode] = useState<"list" | "card">("list");
|
||||
const [positions, setPositions] = useState<HrJobPosition[]>(mockJobPositions)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedLevel, setSelectedLevel] = useState<string>('all')
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
|
||||
const [viewMode, setViewMode] = useState<'list' | 'card'>('list')
|
||||
|
||||
// Modal states
|
||||
const [isFormModalOpen, setIsFormModalOpen] = useState(false);
|
||||
const [isViewModalOpen, setIsViewModalOpen] = useState(false);
|
||||
const [selectedPosition, setSelectedPosition] = useState<
|
||||
HrJobPosition | undefined
|
||||
>(undefined);
|
||||
const [modalTitle, setModalTitle] = useState("");
|
||||
const [isFormModalOpen, setIsFormModalOpen] = useState(false)
|
||||
const [isViewModalOpen, setIsViewModalOpen] = useState(false)
|
||||
const [selectedPosition, setSelectedPosition] = useState<HrJobPosition | undefined>(undefined)
|
||||
const [modalTitle, setModalTitle] = useState('')
|
||||
|
||||
const handleAdd = () => {
|
||||
setSelectedPosition(undefined);
|
||||
setModalTitle("Yeni İş Pozisyonu");
|
||||
setIsFormModalOpen(true);
|
||||
};
|
||||
setSelectedPosition(undefined)
|
||||
setModalTitle('Yeni İş Pozisyonu')
|
||||
setIsFormModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEdit = (position: HrJobPosition) => {
|
||||
setSelectedPosition(position);
|
||||
setModalTitle("İş Pozisyonu Düzenle");
|
||||
setIsFormModalOpen(true);
|
||||
};
|
||||
setSelectedPosition(position)
|
||||
setModalTitle('İş Pozisyonu Düzenle')
|
||||
setIsFormModalOpen(true)
|
||||
}
|
||||
|
||||
const handleView = (position: HrJobPosition) => {
|
||||
setSelectedPosition(position);
|
||||
setIsViewModalOpen(true);
|
||||
};
|
||||
setSelectedPosition(position)
|
||||
setIsViewModalOpen(true)
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if (window.confirm("Bu pozisyonu silmek istediğinizden emin misiniz?")) {
|
||||
setPositions(positions.filter((p) => p.id !== id));
|
||||
if (window.confirm('Bu pozisyonu silmek istediğinizden emin misiniz?')) {
|
||||
setPositions(positions.filter((p) => p.id !== id))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleSavePosition = (positionData: Partial<HrJobPosition>) => {
|
||||
if (selectedPosition) {
|
||||
|
|
@ -63,12 +62,8 @@ const JobPositions: React.FC = () => {
|
|||
...selectedPosition,
|
||||
...positionData,
|
||||
lastModificationTime: new Date(),
|
||||
};
|
||||
setPositions(
|
||||
positions.map((p) =>
|
||||
p.id === selectedPosition.id ? updatedPosition : p
|
||||
)
|
||||
);
|
||||
}
|
||||
setPositions(positions.map((p) => (p.id === selectedPosition.id ? updatedPosition : p)))
|
||||
} else {
|
||||
// Add new position
|
||||
const newPosition: HrJobPosition = {
|
||||
|
|
@ -77,21 +72,21 @@ const JobPositions: React.FC = () => {
|
|||
employees: [],
|
||||
creationTime: new Date(),
|
||||
lastModificationTime: new Date(),
|
||||
} as HrJobPosition;
|
||||
setPositions([...positions, newPosition]);
|
||||
} as HrJobPosition
|
||||
setPositions([...positions, newPosition])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const closeFormModal = () => {
|
||||
setIsFormModalOpen(false);
|
||||
setSelectedPosition(undefined);
|
||||
setModalTitle("");
|
||||
};
|
||||
setIsFormModalOpen(false)
|
||||
setSelectedPosition(undefined)
|
||||
setModalTitle('')
|
||||
}
|
||||
|
||||
const closeViewModal = () => {
|
||||
setIsViewModalOpen(false);
|
||||
setSelectedPosition(undefined);
|
||||
};
|
||||
setIsViewModalOpen(false)
|
||||
setSelectedPosition(undefined)
|
||||
}
|
||||
|
||||
const filteredPositions = positions.filter((position) => {
|
||||
if (
|
||||
|
|
@ -99,34 +94,27 @@ const JobPositions: React.FC = () => {
|
|||
!position.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
|
||||
!position.code.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (selectedLevel !== "all" && position.level !== selectedLevel) {
|
||||
return false;
|
||||
if (selectedLevel !== 'all' && position.level !== selectedLevel) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
selectedDepartment !== "all" &&
|
||||
position.department?.id !== selectedDepartment
|
||||
) {
|
||||
return false;
|
||||
if (selectedDepartment !== 'all' && position.department?.id !== selectedDepartment) {
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
// Card component for individual position
|
||||
const PositionCard: React.FC<{ position: HrJobPosition }> = ({
|
||||
position,
|
||||
}) => (
|
||||
const PositionCard: React.FC<{ position: HrJobPosition }> = ({ position }) => (
|
||||
<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-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h3 className="text-base font-semibold text-gray-900">
|
||||
{position.name}
|
||||
</h3>
|
||||
<h3 className="text-base font-semibold text-gray-900">{position.name}</h3>
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${getJobLevelColor(
|
||||
position.level
|
||||
position.level,
|
||||
)}`}
|
||||
>
|
||||
{getJobLevelText(position.level)}
|
||||
|
|
@ -136,9 +124,9 @@ const JobPositions: React.FC = () => {
|
|||
<p
|
||||
className="text-sm text-gray-700 overflow-hidden"
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitBoxOrient: 'vertical',
|
||||
}}
|
||||
>
|
||||
{position.description}
|
||||
|
|
@ -146,19 +134,17 @@ const JobPositions: React.FC = () => {
|
|||
</div>
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
position.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
position.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{position.isActive ? "Aktif" : "Pasif"}
|
||||
{position.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 mb-3">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<FaBuilding className="w-4 h-4" />
|
||||
<span>{position.department?.name || "Departman belirtilmemiş"}</span>
|
||||
<span>{position.department?.name || 'Departman belirtilmemiş'}</span>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<FaDollarSign className="w-4 h-4" />
|
||||
<span>
|
||||
₺{position.minSalary.toLocaleString()} - ₺
|
||||
{position.maxSalary.toLocaleString()}
|
||||
₺{position.minSalary.toLocaleString()} - ₺{position.maxSalary.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<p className="text-xs font-medium text-gray-500 mb-2">
|
||||
Gerekli Yetenekler
|
||||
</p>
|
||||
<p className="text-xs font-medium text-gray-500 mb-2">Gerekli Yetenekler</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{position.requiredSkills?.slice(0, 4).map((skill, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded"
|
||||
>
|
||||
<span key={index} className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
|
|
@ -219,44 +199,42 @@ const JobPositions: React.FC = () => {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const columns: Column<HrJobPosition>[] = [
|
||||
{
|
||||
key: "code",
|
||||
header: "Pozisyon Kodu",
|
||||
key: 'code',
|
||||
header: 'Pozisyon Kodu',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "title",
|
||||
header: "Pozisyon Adı",
|
||||
key: 'title',
|
||||
header: 'Pozisyon Adı',
|
||||
sortable: true,
|
||||
render: (position: HrJobPosition) => (
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{position.name}</div>
|
||||
<div className="text-sm text-gray-500 truncate max-w-xs">
|
||||
{position.description}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 truncate max-w-xs">{position.description}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "department",
|
||||
header: "Departman",
|
||||
key: 'department',
|
||||
header: 'Departman',
|
||||
render: (position: HrJobPosition) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<FaBuilding className="w-4 h-4 text-gray-500" />
|
||||
<span>{position.department?.name || "-"}</span>
|
||||
<span>{position.department?.name || '-'}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "level",
|
||||
header: "Seviye",
|
||||
key: 'level',
|
||||
header: 'Seviye',
|
||||
render: (position: HrJobPosition) => (
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${getJobLevelColor(
|
||||
position.level
|
||||
position.level,
|
||||
)}`}
|
||||
>
|
||||
{getJobLevelText(position.level)}
|
||||
|
|
@ -264,8 +242,8 @@ const JobPositions: React.FC = () => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "employeeCount",
|
||||
header: "Personel Sayısı",
|
||||
key: 'employeeCount',
|
||||
header: 'Personel Sayısı',
|
||||
render: (position: HrJobPosition) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaUsers className="w-4 h-4 text-gray-500" />
|
||||
|
|
@ -274,30 +252,25 @@ const JobPositions: React.FC = () => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "salary",
|
||||
header: "Maaş Aralığı",
|
||||
key: 'salary',
|
||||
header: 'Maaş Aralığı',
|
||||
render: (position: HrJobPosition) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaDollarSign className="w-4 h-4 text-gray-500" />
|
||||
<div className="text-sm">
|
||||
<div>₺{position.minSalary.toLocaleString()}</div>
|
||||
<div className="text-gray-500">
|
||||
₺{position.maxSalary.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-gray-500">₺{position.maxSalary.toLocaleString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "skills",
|
||||
header: "Gerekli Yetenekler",
|
||||
key: 'skills',
|
||||
header: 'Gerekli Yetenekler',
|
||||
render: (position: HrJobPosition) => (
|
||||
<div className="flex flex-wrap gap-1 max-w-xs">
|
||||
{position.requiredSkills?.slice(0, 3).map((skill, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded"
|
||||
>
|
||||
<span key={index} className="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
|
|
@ -310,23 +283,21 @@ const JobPositions: React.FC = () => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
header: "Durum",
|
||||
key: 'status',
|
||||
header: 'Durum',
|
||||
render: (position: HrJobPosition) => (
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
position.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
position.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{position.isActive ? "Aktif" : "Pasif"}
|
||||
{position.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
header: "İşlemler",
|
||||
key: 'actions',
|
||||
header: 'İşlemler',
|
||||
render: (position: HrJobPosition) => (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
|
|
@ -353,196 +324,173 @@ const JobPositions: React.FC = () => {
|
|||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
// Get unique departments for filter
|
||||
const departments = [
|
||||
...new Set(positions.map((p) => p.department).filter(Boolean)),
|
||||
];
|
||||
const departments = [...new Set(positions.map((p) => p.department).filter(Boolean))]
|
||||
|
||||
return (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">İş Pozisyonları</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Şirket pozisyonları ve iş tanımları yönetimi
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* View Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-0.5">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">İş Pozisyonları</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Şirket pozisyonları ve iş tanımları yönetimi
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* View Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-0.5">
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${
|
||||
viewMode === 'list'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('card')}
|
||||
className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${
|
||||
viewMode === 'card'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${
|
||||
viewMode === "list"
|
||||
? "bg-white text-gray-900 shadow-sm"
|
||||
: "text-gray-500 hover:text-gray-700"
|
||||
}`}
|
||||
onClick={handleAdd}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("card")}
|
||||
className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors ${
|
||||
viewMode === "card"
|
||||
? "bg-white text-gray-900 shadow-sm"
|
||||
: "text-gray-500 hover:text-gray-700"
|
||||
}`}
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
<FaPlus className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Yeni Pozisyon</span>
|
||||
<span className="sm:hidden">Yeni</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Yeni Pozisyon</span>
|
||||
<span className="sm:hidden">Yeni</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaBriefcase className="w-6 h-6 text-blue-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">
|
||||
Toplam Pozisyon
|
||||
</p>
|
||||
<p className="text-xl font-bold text-gray-900">
|
||||
{positions.length}
|
||||
</p>
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaBriefcase className="w-6 h-6 text-blue-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">Toplam Pozisyon</p>
|
||||
<p className="text-xl font-bold text-gray-900">{positions.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaUsers className="w-6 h-6 text-green-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">Dolu Pozisyonlar</p>
|
||||
<p className="text-xl font-bold text-gray-900">
|
||||
{positions.filter((p) => p.employees && p.employees.length > 0).length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaBriefcase className="w-6 h-6 text-orange-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">Boş Pozisyonlar</p>
|
||||
<p className="text-xl font-bold text-gray-900">
|
||||
{positions.filter((p) => !p.employees || p.employees.length === 0).length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaBuilding className="w-6 h-6 text-purple-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">Departman Sayısı</p>
|
||||
<p className="text-xl font-bold text-gray-900">{departments.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaUsers className="w-6 h-6 text-green-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">
|
||||
Dolu Pozisyonlar
|
||||
</p>
|
||||
<p className="text-xl font-bold text-gray-900">
|
||||
{
|
||||
positions.filter((p) => p.employees && p.employees.length > 0)
|
||||
.length
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pozisyon adı veya kodu ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<select
|
||||
value={selectedLevel}
|
||||
onChange={(e) => setSelectedLevel(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[160px]"
|
||||
>
|
||||
<option value="all">Tüm Seviyeler</option>
|
||||
<option value={JobLevelEnum.Entry}>Giriş Seviyesi</option>
|
||||
<option value={JobLevelEnum.Junior}>Junior</option>
|
||||
<option value={JobLevelEnum.Mid}>Orta Seviye</option>
|
||||
<option value={JobLevelEnum.Senior}>Senior</option>
|
||||
<option value={JobLevelEnum.Lead}>Lider</option>
|
||||
<option value={JobLevelEnum.Manager}>Yönetici</option>
|
||||
<option value={JobLevelEnum.Director}>Direktör</option>
|
||||
<option value={JobLevelEnum.Executive}>Üst Düzey Yönetici</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
value={selectedDepartment}
|
||||
onChange={(e) => setSelectedDepartment(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[160px]"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{departments.map((dept) => (
|
||||
<option key={dept?.id} value={dept?.id}>
|
||||
{dept?.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaBriefcase className="w-6 h-6 text-orange-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">
|
||||
Boş Pozisyonlar
|
||||
</p>
|
||||
<p className="text-xl font-bold text-gray-900">
|
||||
{
|
||||
positions.filter(
|
||||
(p) => !p.employees || p.employees.length === 0
|
||||
).length
|
||||
}
|
||||
</p>
|
||||
{/* Content - List or Card View */}
|
||||
{viewMode === 'list' ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<DataTable data={filteredPositions} columns={columns} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border">
|
||||
<div className="flex items-center">
|
||||
<FaBuilding className="w-6 h-6 text-purple-600" />
|
||||
<div className="ml-3">
|
||||
<p className="text-xs font-medium text-gray-600">
|
||||
Departman Sayısı
|
||||
</p>
|
||||
<p className="text-xl font-bold text-gray-900">
|
||||
{departments.length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pozisyon adı veya kodu ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<select
|
||||
value={selectedLevel}
|
||||
onChange={(e) => setSelectedLevel(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[160px]"
|
||||
>
|
||||
<option value="all">Tüm Seviyeler</option>
|
||||
<option value={JobLevelEnum.Entry}>Giriş Seviyesi</option>
|
||||
<option value={JobLevelEnum.Junior}>Junior</option>
|
||||
<option value={JobLevelEnum.Mid}>Orta Seviye</option>
|
||||
<option value={JobLevelEnum.Senior}>Senior</option>
|
||||
<option value={JobLevelEnum.Lead}>Lider</option>
|
||||
<option value={JobLevelEnum.Manager}>Yönetici</option>
|
||||
<option value={JobLevelEnum.Director}>Direktör</option>
|
||||
<option value={JobLevelEnum.Executive}>Üst Düzey Yönetici</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
value={selectedDepartment}
|
||||
onChange={(e) => setSelectedDepartment(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-0 sm:min-w-[160px]"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{departments.map((dept) => (
|
||||
<option key={dept?.id} value={dept?.id}>
|
||||
{dept?.name}
|
||||
</option>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filteredPositions.map((position) => (
|
||||
<PositionCard key={position.id} position={position} />
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content - List or Card View */}
|
||||
{viewMode === "list" ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<DataTable data={filteredPositions} columns={columns} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filteredPositions.map((position) => (
|
||||
<PositionCard key={position.id} position={position} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Empty State */}
|
||||
{filteredPositions.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaBriefcase className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
||||
Pozisyon bulunamadı
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
Arama kriterlerinizi değiştirmeyi deneyin.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{/* Empty State */}
|
||||
{filteredPositions.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaBriefcase className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">Pozisyon bulunamadı</h3>
|
||||
<p className="text-sm text-gray-500">Arama kriterlerinizi değiştirmeyi deneyin.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<JobPositionFormModal
|
||||
|
|
@ -558,8 +506,8 @@ const JobPositions: React.FC = () => {
|
|||
onClose={closeViewModal}
|
||||
position={selectedPosition}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default JobPositions;
|
||||
export default JobPositions
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import React, { useState, useEffect, useMemo } from 'react'
|
||||
import {
|
||||
FaUser,
|
||||
FaUsers,
|
||||
|
|
@ -14,54 +14,49 @@ import {
|
|||
FaPhone,
|
||||
FaMapMarkerAlt,
|
||||
FaBriefcase,
|
||||
} from "react-icons/fa";
|
||||
import { HrEmployee, HrOrganizationChart as OrgChart } from "../../../types/hr";
|
||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||||
import { mockDepartments } from "../../../mocks/mockDepartments";
|
||||
import Widget from "../../../components/common/Widget";
|
||||
} from 'react-icons/fa'
|
||||
import { HrEmployee, HrOrganizationChart as OrgChart } from '../../../types/hr'
|
||||
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||
import { mockDepartments } from '../../../mocks/mockDepartments'
|
||||
import Widget from '../../../components/common/Widget'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
// Dinamik organizasyon verisi oluşturma fonksiyonu
|
||||
const generateOrganizationData = (): OrgChart[] => {
|
||||
const orgData: OrgChart[] = [];
|
||||
const orgData: OrgChart[] = []
|
||||
|
||||
// Çalışanları işle
|
||||
mockEmployees.forEach((employee) => {
|
||||
const department = mockDepartments.find(
|
||||
(d) => d.id === employee.departmantId
|
||||
);
|
||||
const department = mockDepartments.find((d) => d.id === employee.departmantId)
|
||||
|
||||
let level = 3; // Varsayılan seviye (normal çalışan)
|
||||
let parentId: string | undefined = undefined;
|
||||
let level = 3 // Varsayılan seviye (normal çalışan)
|
||||
let parentId: string | undefined = undefined
|
||||
|
||||
// Eğer bu çalışan bir departman yöneticisiyse
|
||||
if (department && department.managerId === employee.id) {
|
||||
if (!department.parentDepartmentId) {
|
||||
// Ana departman yöneticisi (CEO/Genel Müdür)
|
||||
level = 0;
|
||||
level = 0
|
||||
} else {
|
||||
// Alt departman yöneticisi
|
||||
level = 1;
|
||||
level = 1
|
||||
// Üst departmanın yöneticisini parent olarak bul
|
||||
const parentDept = mockDepartments.find(
|
||||
(d) => d.id === department.parentDepartmentId
|
||||
);
|
||||
const parentDept = mockDepartments.find((d) => d.id === department.parentDepartmentId)
|
||||
if (parentDept && parentDept.managerId) {
|
||||
parentId = parentDept.managerId;
|
||||
parentId = parentDept.managerId
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal çalışan - departman yöneticisine bağlı
|
||||
if (department && department.managerId) {
|
||||
parentId = department.managerId;
|
||||
parentId = department.managerId
|
||||
|
||||
// Departman yöneticisinin seviyesine göre belirleme
|
||||
const managerDept = mockDepartments.find(
|
||||
(d) => d.managerId === department.managerId
|
||||
);
|
||||
const managerDept = mockDepartments.find((d) => d.managerId === department.managerId)
|
||||
if (managerDept && !managerDept.parentDepartmentId) {
|
||||
level = 2; // CEO'ya direkt bağlı
|
||||
level = 2 // CEO'ya direkt bağlı
|
||||
} 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,
|
||||
parentId: parentId,
|
||||
level: level,
|
||||
position: employee.jobPosition?.name || "Belirsiz",
|
||||
position: employee.jobPosition?.name || 'Belirsiz',
|
||||
isActive: employee.isActive,
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
return orgData.sort((a, b) => a.level - b.level);
|
||||
};
|
||||
return orgData.sort((a, b) => a.level - b.level)
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
employee: HrEmployee;
|
||||
children: TreeNode[];
|
||||
level: number;
|
||||
employee: HrEmployee
|
||||
children: TreeNode[]
|
||||
level: number
|
||||
}
|
||||
|
||||
const OrganizationChart: React.FC = () => {
|
||||
const [employees] = useState<HrEmployee[]>(mockEmployees);
|
||||
const [organizationData] = useState<OrgChart[]>(generateOrganizationData());
|
||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
|
||||
const [orgTree, setOrgTree] = useState<TreeNode[]>([]);
|
||||
const [viewMode, setViewMode] = useState<"tree" | "cards">("tree");
|
||||
const [selectedEmployee, setSelectedEmployee] = useState<HrEmployee | null>(
|
||||
null
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<string>("all");
|
||||
const [employees] = useState<HrEmployee[]>(mockEmployees)
|
||||
const [organizationData] = useState<OrgChart[]>(generateOrganizationData())
|
||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set())
|
||||
const [orgTree, setOrgTree] = useState<TreeNode[]>([])
|
||||
const [viewMode, setViewMode] = useState<'tree' | 'cards'>('tree')
|
||||
const [selectedEmployee, setSelectedEmployee] = useState<HrEmployee | null>(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [selectedDepartment, setSelectedDepartment] = useState<string>('all')
|
||||
|
||||
const handleViewEmployee = (employee: HrEmployee) => {
|
||||
setSelectedEmployee(employee);
|
||||
setShowModal(true);
|
||||
};
|
||||
setSelectedEmployee(employee)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setShowModal(false);
|
||||
setSelectedEmployee(null);
|
||||
};
|
||||
setShowModal(false)
|
||||
setSelectedEmployee(null)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Build organization tree structure
|
||||
const buildTree = (): TreeNode[] => {
|
||||
const nodeMap = new Map<string, TreeNode>();
|
||||
const nodeMap = new Map<string, TreeNode>()
|
||||
|
||||
// First, create all nodes
|
||||
employees.forEach((employee) => {
|
||||
|
|
@ -119,82 +112,79 @@ const OrganizationChart: React.FC = () => {
|
|||
employee,
|
||||
children: [],
|
||||
level: 0,
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
// Then, build the tree structure
|
||||
const rootNodes: TreeNode[] = [];
|
||||
const rootNodes: TreeNode[] = []
|
||||
|
||||
organizationData.forEach((orgData) => {
|
||||
const node = nodeMap.get(orgData.employeeId);
|
||||
const node = nodeMap.get(orgData.employeeId)
|
||||
if (node) {
|
||||
node.level = orgData.level;
|
||||
node.level = orgData.level
|
||||
|
||||
if (orgData.parentId) {
|
||||
const parentNode = nodeMap.get(orgData.parentId);
|
||||
const parentNode = nodeMap.get(orgData.parentId)
|
||||
if (parentNode) {
|
||||
parentNode.children.push(node);
|
||||
parentNode.children.push(node)
|
||||
}
|
||||
} else {
|
||||
rootNodes.push(node);
|
||||
rootNodes.push(node)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return rootNodes;
|
||||
};
|
||||
return rootNodes
|
||||
}
|
||||
|
||||
setOrgTree(buildTree());
|
||||
setOrgTree(buildTree())
|
||||
// Auto-expand first level
|
||||
const firstLevelIds = organizationData
|
||||
.filter((org) => org.level <= 1)
|
||||
.map((org) => org.employeeId);
|
||||
setExpandedNodes(new Set(firstLevelIds));
|
||||
}, [employees, organizationData]);
|
||||
.map((org) => org.employeeId)
|
||||
setExpandedNodes(new Set(firstLevelIds))
|
||||
}, [employees, organizationData])
|
||||
|
||||
// Filtered data for views
|
||||
const filteredOrgTree = useMemo(() => {
|
||||
if (selectedDepartment === "all") {
|
||||
return orgTree;
|
||||
if (selectedDepartment === 'all') {
|
||||
return orgTree
|
||||
}
|
||||
|
||||
const filterTree = (nodes: TreeNode[]): TreeNode[] => {
|
||||
const result: TreeNode[] = [];
|
||||
const result: TreeNode[] = []
|
||||
for (const node of nodes) {
|
||||
const filteredChildren = filterTree(node.children);
|
||||
if (
|
||||
node.employee.departmantId === selectedDepartment ||
|
||||
filteredChildren.length > 0
|
||||
) {
|
||||
result.push({ ...node, children: filteredChildren });
|
||||
const filteredChildren = filterTree(node.children)
|
||||
if (node.employee.departmantId === selectedDepartment || filteredChildren.length > 0) {
|
||||
result.push({ ...node, children: filteredChildren })
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return result
|
||||
}
|
||||
|
||||
return filterTree(orgTree);
|
||||
}, [orgTree, selectedDepartment]);
|
||||
return filterTree(orgTree)
|
||||
}, [orgTree, selectedDepartment])
|
||||
|
||||
const filteredEmployees = useMemo(() => {
|
||||
if (selectedDepartment === "all") {
|
||||
return employees;
|
||||
if (selectedDepartment === 'all') {
|
||||
return employees
|
||||
}
|
||||
return employees.filter((emp) => emp.departmantId === selectedDepartment);
|
||||
}, [employees, selectedDepartment]);
|
||||
return employees.filter((emp) => emp.departmantId === selectedDepartment)
|
||||
}, [employees, selectedDepartment])
|
||||
|
||||
const toggleNode = (nodeId: string) => {
|
||||
const newExpanded = new Set(expandedNodes);
|
||||
const newExpanded = new Set(expandedNodes)
|
||||
if (newExpanded.has(nodeId)) {
|
||||
newExpanded.delete(nodeId);
|
||||
newExpanded.delete(nodeId)
|
||||
} else {
|
||||
newExpanded.add(nodeId);
|
||||
newExpanded.add(nodeId)
|
||||
}
|
||||
setExpandedNodes(newExpanded);
|
||||
};
|
||||
setExpandedNodes(newExpanded)
|
||||
}
|
||||
|
||||
const renderTreeNode = (node: TreeNode): React.ReactNode => {
|
||||
const isExpanded = expandedNodes.has(node.employee.id);
|
||||
const hasChildren = node.children.length > 0;
|
||||
const isExpanded = expandedNodes.has(node.employee.id)
|
||||
const hasChildren = node.children.length > 0
|
||||
|
||||
return (
|
||||
<div key={node.employee.id} className="relative">
|
||||
|
|
@ -205,7 +195,7 @@ const OrganizationChart: React.FC = () => {
|
|||
style={{
|
||||
left: `${node.level * 1.25 - 0.7}rem`,
|
||||
top: 0,
|
||||
height: "100%",
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -217,7 +207,7 @@ const OrganizationChart: React.FC = () => {
|
|||
{node.level > 0 && (
|
||||
<span
|
||||
className="absolute border-t border-gray-300 w-3"
|
||||
style={{ left: `${node.level * 1.25 - 0.7}rem`, top: "1.1rem" }}
|
||||
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)}
|
||||
className="p-1 text-gray-500 hover:text-gray-800 rounded-full hover:bg-gray-200"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<FaChevronDown size={10} />
|
||||
) : (
|
||||
<FaChevronRight size={10} />
|
||||
)}
|
||||
{isExpanded ? <FaChevronDown size={10} /> : <FaChevronRight size={10} />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -248,9 +234,7 @@ const OrganizationChart: React.FC = () => {
|
|||
<p className="text-xs text-gray-600 truncate">
|
||||
{node.employee.jobPosition?.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
{node.employee.department?.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">{node.employee.department?.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -279,22 +263,23 @@ const OrganizationChart: React.FC = () => {
|
|||
<div>{node.children.map((child) => renderTreeNode(child))}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const renderCardView = () => {
|
||||
const groupedByLevel = filteredEmployees.reduce((acc, employee) => {
|
||||
const orgData = organizationData.find(
|
||||
(org) => org.employeeId === employee.id
|
||||
);
|
||||
const level = orgData?.level || 0;
|
||||
const groupedByLevel = filteredEmployees.reduce(
|
||||
(acc, employee) => {
|
||||
const orgData = organizationData.find((org) => org.employeeId === employee.id)
|
||||
const level = orgData?.level || 0
|
||||
|
||||
if (!acc[level]) {
|
||||
acc[level] = [];
|
||||
}
|
||||
acc[level].push(employee);
|
||||
return acc;
|
||||
}, {} as Record<number, HrEmployee[]>);
|
||||
if (!acc[level]) {
|
||||
acc[level] = []
|
||||
}
|
||||
acc[level].push(employee)
|
||||
return acc
|
||||
},
|
||||
{} as Record<number, HrEmployee[]>,
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
|
@ -304,13 +289,13 @@ const OrganizationChart: React.FC = () => {
|
|||
<div key={level}>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<FaBuilding className="w-5 h-5" />
|
||||
{level === "0"
|
||||
? "Üst Yönetim"
|
||||
: level === "1"
|
||||
? "Orta Kademe Yönetim"
|
||||
: level === "2"
|
||||
? "Alt Kademe Yönetim"
|
||||
: `${parseInt(level) + 1}. Seviye`}
|
||||
{level === '0'
|
||||
? 'Üst Yönetim'
|
||||
: level === '1'
|
||||
? 'Orta Kademe Yönetim'
|
||||
: level === '2'
|
||||
? 'Alt Kademe Yönetim'
|
||||
: `${parseInt(level) + 1}. Seviye`}
|
||||
<span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
|
||||
{levelEmployees.length} kişi
|
||||
</span>
|
||||
|
|
@ -325,20 +310,20 @@ const OrganizationChart: React.FC = () => {
|
|||
<div className="flex items-center mb-3">
|
||||
<div
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center mr-2 ${
|
||||
level === "0"
|
||||
? "bg-blue-100"
|
||||
: level === "1"
|
||||
? "bg-green-100"
|
||||
: "bg-gray-100"
|
||||
level === '0'
|
||||
? 'bg-blue-100'
|
||||
: level === '1'
|
||||
? 'bg-green-100'
|
||||
: 'bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<FaUser
|
||||
className={`w-4 h-4 ${
|
||||
level === "0"
|
||||
? "text-blue-600"
|
||||
: level === "1"
|
||||
? "text-green-600"
|
||||
: "text-gray-600"
|
||||
level === '0'
|
||||
? 'text-blue-600'
|
||||
: level === '1'
|
||||
? 'text-green-600'
|
||||
: 'text-gray-600'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -350,9 +335,7 @@ const OrganizationChart: React.FC = () => {
|
|||
>
|
||||
{employee.fullName}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 truncate">
|
||||
{employee.code}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 truncate">{employee.code}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -360,9 +343,7 @@ const OrganizationChart: React.FC = () => {
|
|||
<p className="text-xs text-gray-800 font-medium truncate">
|
||||
{employee.jobPosition?.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 truncate">
|
||||
{employee.department?.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 truncate">{employee.department?.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -370,113 +351,100 @@ const OrganizationChart: React.FC = () => {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3 pt-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">
|
||||
Organizasyon Şeması
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Kurumsal hiyerarşi ve raporlama yapısı
|
||||
</p>
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">Organizasyon Şeması</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">Kurumsal hiyerarşi ve raporlama yapısı</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={selectedDepartment}
|
||||
onChange={(e) => setSelectedDepartment(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{mockDepartments.map((dept) => (
|
||||
<option key={dept.id} value={dept.id}>
|
||||
{dept.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex bg-gray-100 rounded-lg p-0.5">
|
||||
<button
|
||||
onClick={() => setViewMode('tree')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'tree'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
<FaSitemap className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('cards')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'cards'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={selectedDepartment}
|
||||
onChange={(e) => setSelectedDepartment(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="all">Tüm Departmanlar</option>
|
||||
{mockDepartments.map((dept) => (
|
||||
<option key={dept.id} value={dept.id}>
|
||||
{dept.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex bg-gray-100 rounded-lg p-0.5">
|
||||
<button
|
||||
onClick={() => setViewMode("tree")}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === "tree"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-600 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<FaSitemap className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("cards")}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === "cards"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-600 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<Widget title="Toplam Personel" value={employees.length} color="blue" icon="FaBuilding" />
|
||||
|
||||
<Widget
|
||||
title="Departmanlar"
|
||||
value={new Set(employees.map((e) => e.department?.id).filter(Boolean)).size}
|
||||
color="purple"
|
||||
icon="FaBuilding"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Seviye Sayısı"
|
||||
value={
|
||||
organizationData.length > 0
|
||||
? Math.max(...organizationData.map((org) => org.level)) + 1
|
||||
: 0
|
||||
}
|
||||
color="orange"
|
||||
icon="FaUser"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<Widget
|
||||
title="Toplam Personel"
|
||||
value={employees.length}
|
||||
color="blue"
|
||||
icon="FaBuilding"
|
||||
/>
|
||||
{/* Content */}
|
||||
<div className="bg-white rounded-lg border p-4">
|
||||
{viewMode === 'tree' ? (
|
||||
<div className="pt-2">{filteredOrgTree.map((node) => renderTreeNode(node))}</div>
|
||||
) : (
|
||||
renderCardView()
|
||||
)}
|
||||
|
||||
<Widget
|
||||
title="Departmanlar"
|
||||
value={
|
||||
new Set(employees.map((e) => e.department?.id).filter(Boolean)).size
|
||||
}
|
||||
color="purple"
|
||||
icon="FaBuilding"
|
||||
/>
|
||||
|
||||
<Widget
|
||||
title="Seviye Sayısı"
|
||||
value={
|
||||
organizationData.length > 0
|
||||
? Math.max(...organizationData.map((org) => org.level)) + 1
|
||||
: 0
|
||||
}
|
||||
color="orange"
|
||||
icon="FaUser"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="bg-white rounded-lg border p-4">
|
||||
{viewMode === "tree" ? (
|
||||
<div className="pt-2">
|
||||
{filteredOrgTree.map((node) => renderTreeNode(node))}
|
||||
</div>
|
||||
) : (
|
||||
renderCardView()
|
||||
)}
|
||||
|
||||
{(viewMode === "tree"
|
||||
? filteredOrgTree.length === 0
|
||||
: filteredEmployees.length === 0) && (
|
||||
<div className="text-center py-12">
|
||||
<FaBuilding className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
||||
Organizasyon verisi bulunamadı
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
Henüz organizasyon şeması oluşturulmamış.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{(viewMode === 'tree'
|
||||
? filteredOrgTree.length === 0
|
||||
: filteredEmployees.length === 0) && (
|
||||
<div className="text-center py-12">
|
||||
<FaBuilding className="w-10 h-10 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
||||
Organizasyon verisi bulunamadı
|
||||
</h3>
|
||||
<p className="text-gray-500">Henüz organizasyon şeması oluşturulmamış.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Employee Detail Modal */}
|
||||
|
|
@ -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">
|
||||
{/* Modal Header */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Personel Detayları
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleCloseModal}
|
||||
className="text-gray-400 hover:text-gray-600 p-1"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Personel Detayları</h3>
|
||||
<button onClick={handleCloseModal} className="text-gray-400 hover:text-gray-600 p-1">
|
||||
<FaTimes className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -504,13 +467,9 @@ const OrganizationChart: React.FC = () => {
|
|||
<FaUser className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-gray-900">
|
||||
{selectedEmployee.fullName}
|
||||
</h4>
|
||||
<h4 className="text-lg font-bold text-gray-900">{selectedEmployee.fullName}</h4>
|
||||
<p className="text-gray-600">{selectedEmployee.code}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{selectedEmployee.jobPosition?.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">{selectedEmployee.jobPosition?.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -524,17 +483,13 @@ const OrganizationChart: React.FC = () => {
|
|||
<div className="flex items-center">
|
||||
<FaEnvelope className="w-4 h-4 text-gray-500 mr-3" />
|
||||
<span className="text-sm">E-posta:</span>
|
||||
<span className="ml-2 text-sm font-medium">
|
||||
{selectedEmployee.email}
|
||||
</span>
|
||||
<span className="ml-2 text-sm font-medium">{selectedEmployee.email}</span>
|
||||
</div>
|
||||
{selectedEmployee.phone && (
|
||||
<div className="flex items-center">
|
||||
<FaPhone className="w-4 h-4 text-gray-500 mr-3" />
|
||||
<span className="text-sm">İş Telefonu:</span>
|
||||
<span className="ml-2 text-sm font-medium">
|
||||
{selectedEmployee.phone}
|
||||
</span>
|
||||
<span className="ml-2 text-sm font-medium">{selectedEmployee.phone}</span>
|
||||
</div>
|
||||
)}
|
||||
{selectedEmployee.personalPhone && (
|
||||
|
|
@ -559,39 +514,25 @@ const OrganizationChart: React.FC = () => {
|
|||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">Departman:</span>
|
||||
<p className="font-medium">
|
||||
{selectedEmployee.department?.name}
|
||||
</p>
|
||||
<p className="font-medium">{selectedEmployee.department?.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">Pozisyon:</span>
|
||||
<p className="font-medium">
|
||||
{selectedEmployee.jobPosition?.name}
|
||||
</p>
|
||||
<p className="font-medium">{selectedEmployee.jobPosition?.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">
|
||||
İşe Başlama Tarihi:
|
||||
</span>
|
||||
<span className="text-sm text-gray-600">İşe Başlama Tarihi:</span>
|
||||
<p className="font-medium flex items-center">
|
||||
<FaCalendar className="w-4 h-4 mr-1 text-gray-500" />
|
||||
{new Date(selectedEmployee.hireDate).toLocaleDateString(
|
||||
"tr-TR"
|
||||
)}
|
||||
{new Date(selectedEmployee.hireDate).toLocaleDateString('tr-TR')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">
|
||||
İstihdam Türü:
|
||||
</span>
|
||||
<p className="font-medium">
|
||||
{selectedEmployee.employmentType}
|
||||
</p>
|
||||
<span className="text-sm text-gray-600">İstihdam Türü:</span>
|
||||
<p className="font-medium">{selectedEmployee.employmentType}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">
|
||||
Çalışma Lokasyonu:
|
||||
</span>
|
||||
<span className="text-sm text-gray-600">Çalışma Lokasyonu:</span>
|
||||
<p className="font-medium flex items-center">
|
||||
<FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500" />
|
||||
{selectedEmployee.workLocation}
|
||||
|
|
@ -602,11 +543,11 @@ const OrganizationChart: React.FC = () => {
|
|||
<span
|
||||
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
|
||||
selectedEmployee.isActive
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{selectedEmployee.isActive ? "Aktif" : "Pasif"}
|
||||
{selectedEmployee.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -622,14 +563,10 @@ const OrganizationChart: React.FC = () => {
|
|||
<div className="bg-gray-50 p-3 rounded-lg space-y-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">
|
||||
Doğum Tarihi:
|
||||
</span>
|
||||
<span className="text-sm text-gray-600">Doğum Tarihi:</span>
|
||||
<p className="font-medium flex items-center">
|
||||
<FaCalendar className="w-4 h-4 mr-1 text-gray-500" />
|
||||
{new Date(
|
||||
selectedEmployee.birthDate
|
||||
).toLocaleDateString("tr-TR")}
|
||||
{new Date(selectedEmployee.birthDate).toLocaleDateString('tr-TR')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -637,20 +574,12 @@ const OrganizationChart: React.FC = () => {
|
|||
<p className="font-medium">{selectedEmployee.gender}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">
|
||||
Medeni Durum:
|
||||
</span>
|
||||
<p className="font-medium">
|
||||
{selectedEmployee.maritalStatus}
|
||||
</p>
|
||||
<span className="text-sm text-gray-600">Medeni Durum:</span>
|
||||
<p className="font-medium">{selectedEmployee.maritalStatus}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">
|
||||
TC Kimlik No:
|
||||
</span>
|
||||
<p className="font-medium">
|
||||
{selectedEmployee.nationalId}
|
||||
</p>
|
||||
<span className="text-sm text-gray-600">TC Kimlik No:</span>
|
||||
<p className="font-medium">{selectedEmployee.nationalId}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -659,8 +588,7 @@ const OrganizationChart: React.FC = () => {
|
|||
<span className="text-sm text-gray-600">Adres:</span>
|
||||
<p className="font-medium flex items-start">
|
||||
<FaMapMarkerAlt className="w-4 h-4 mr-1 text-gray-500 mt-0.5" />
|
||||
{selectedEmployee.address.street},{" "}
|
||||
{selectedEmployee.address.city},{" "}
|
||||
{selectedEmployee.address.street}, {selectedEmployee.address.city},{' '}
|
||||
{selectedEmployee.address.country}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -679,9 +607,7 @@ const OrganizationChart: React.FC = () => {
|
|||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">İsim:</span>
|
||||
<p className="font-medium">
|
||||
{selectedEmployee.emergencyContact.name}
|
||||
</p>
|
||||
<p className="font-medium">{selectedEmployee.emergencyContact.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">Yakınlık:</span>
|
||||
|
|
@ -691,9 +617,7 @@ const OrganizationChart: React.FC = () => {
|
|||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600">Telefon:</span>
|
||||
<p className="font-medium">
|
||||
{selectedEmployee.emergencyContact.phone}
|
||||
</p>
|
||||
<p className="font-medium">{selectedEmployee.emergencyContact.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -713,8 +637,8 @@ const OrganizationChart: React.FC = () => {
|
|||
</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
Loading…
Reference in a new issue