Files
personel/src/components/employees/EmployeeTable.tsx
T
2026-03-18 15:42:02 +03:00

327 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import {
UsersIcon,
TrashIcon,
PencilSquareIcon,
PlusIcon,
MagnifyingGlassIcon,
UserPlusIcon,
KeyIcon,
PhoneIcon,
EnvelopeIcon,
CalendarDaysIcon,
IdentificationIcon,
BuildingOffice2Icon
} from '@heroicons/react/24/outline';
import { deleteEmployee } from '@/app/employees/actions';
import EmployeeModal from '@/components/employees/EmployeeModal';
import UserCreationModal from '@/components/employees/UserCreationModal';
interface EmployeeTableProps {
initialEmployees: any[];
companies: any[];
roles: any[];
departments: any[];
sections: any[];
employmentTypes: any[];
jobTitles: any[];
}
export default function EmployeeTable({
initialEmployees,
companies,
roles,
departments,
sections,
employmentTypes,
jobTitles
}: EmployeeTableProps) {
const [employees, setEmployees] = useState(initialEmployees);
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [isModalOpen, setIsModalOpen] = useState(false);
const [isUserModalOpen, setIsUserModalOpen] = useState(false);
const [editingEmployee, setEditingEmployee] = useState<any>(null);
useEffect(() => {
setEmployees(initialEmployees);
}, [initialEmployees]);
const filteredEmployees = employees.filter(emp => {
const fullName = `${emp.first_name || ''} ${emp.last_name || ''}`.toLowerCase();
const email = (emp.email || '').toLowerCase();
const tcNo = (emp.tc_no || '').toLowerCase();
const matchesSearch = fullName.includes(searchTerm.toLowerCase()) ||
email.includes(searchTerm.toLowerCase()) ||
tcNo.includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'all' || emp.status === statusFilter;
return matchesSearch && matchesStatus;
});
const employeesWithoutUser = employees.filter(emp => !emp.user_id && emp.email);
const openEditModal = (emp: any) => {
setEditingEmployee(emp);
setIsModalOpen(true);
};
const handleAdd = () => {
setEditingEmployee(null);
setIsModalOpen(true);
};
const handleDelete = async (id: string) => {
if (confirm('Bu personeli silmek istediğinize emin misiniz?')) {
const result = await deleteEmployee(id);
if (result.error) {
alert(result.error);
}
}
};
const formatDate = (dateString: string | null) => {
if (!dateString) return '-';
return new Date(dateString).toLocaleDateString('tr-TR');
};
return (
<div className="space-y-8">
{/* Header with Add Button */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6">
<div className="sm:flex-auto">
<div className="flex items-center gap-4">
<div className="w-1.5 h-10 bg-[#173363] rounded-full" />
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-slate-900 dark:text-white tracking-tight">Personeller</h1>
<p className="text-xs sm:text-sm font-medium text-slate-500 dark:text-slate-400 mt-1">
Şirketinizdeki tüm personel kayıtlarını yönetin
</p>
</div>
</div>
</div>
<div className="flex flex-col xs:flex-row gap-3">
<button
onClick={() => setIsUserModalOpen(true)}
className="group flex items-center justify-center gap-2 bg-slate-100 dark:bg-zinc-800 hover:bg-[#173363] text-[#173363] dark:text-slate-300 hover:text-white px-6 py-3.5 rounded-full font-black transition-all duration-500 active:scale-95 text-[10px] tracking-widest"
>
<UserPlusIcon className="w-4 h-4" />
KULLANICI TANIMLA
</button>
<button
onClick={handleAdd}
className="group flex items-center justify-center gap-2 bg-[#173363] hover:bg-[#CE0515] text-white px-6 py-3.5 rounded-full font-black shadow-lg shadow-blue-900/20 transition-all duration-500 active:scale-95 text-[10px] tracking-widest"
>
<PlusIcon className="w-4 h-4" />
YENİ PERSONEL
</button>
</div>
</div>
{/* Filter & Search Bar */}
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between bg-white dark:bg-zinc-900 p-4 sm:p-6 rounded-3xl sm:rounded-[2rem] border border-slate-100 dark:border-zinc-800 shadow-sm">
<div className="relative w-full lg:w-96 group">
<MagnifyingGlassIcon className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 group-focus-within:text-[#CE0515] transition-colors" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="İsim, TC veya e-posta..."
className="w-full pl-12 pr-4 py-3 bg-slate-50 dark:bg-zinc-800 border-none rounded-2xl text-sm font-medium focus:ring-2 focus:ring-[#CE0515] transition-all outline-none placeholder:text-slate-400 dark:text-white"
/>
</div>
<div className="flex gap-2 w-full lg:w-auto p-1.5 bg-slate-50 dark:bg-zinc-800 rounded-full border border-slate-100 dark:border-zinc-700 overflow-x-auto no-scrollbar">
<button
onClick={() => setStatusFilter('all')}
className={`flex-1 lg:flex-none whitespace-nowrap px-6 py-2.5 rounded-full text-xs font-black uppercase tracking-wider transition-all duration-500 ${statusFilter === 'all' ? 'bg-[#173363] text-white shadow-md' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-300'}`}
>
Tümü
</button>
<button
onClick={() => setStatusFilter('active')}
className={`flex-1 lg:flex-none whitespace-nowrap px-6 py-2.5 rounded-full text-xs font-black uppercase tracking-wider transition-all duration-500 ${statusFilter === 'active' ? 'bg-emerald-600 text-white shadow-md' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-300'}`}
>
Aktif
</button>
<button
onClick={() => setStatusFilter('inactive')}
className={`flex-1 lg:flex-none whitespace-nowrap px-6 py-2.5 rounded-full text-xs font-black uppercase tracking-wider transition-all duration-500 ${statusFilter === 'inactive' ? 'bg-[#CE0515] text-white shadow-md' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-300'}`}
>
Ayrılan
</button>
</div>
</div>
{/* Table Section */}
<div className="bg-white dark:bg-zinc-900 shadow-sm border border-slate-100 dark:border-zinc-800 rounded-[2.5rem] overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-slate-50 dark:divide-zinc-800">
<thead className="bg-slate-50/50 dark:bg-zinc-800/50">
<tr>
<th className="py-6 pl-8 pr-3 text-left text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">Personel</th>
<th className="px-3 py-6 text-left text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">Kurumsal</th>
<th className="px-3 py-6 text-left text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">Bölüm / Görev</th>
<th className="px-3 py-6 text-left text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">İletişim</th>
<th className="px-3 py-6 text-left text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">Tarihler</th>
<th className="px-3 py-6 text-left text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">Durum</th>
<th className="relative py-6 pl-3 pr-8 text-right">
<span className="sr-only">İşlemler</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-50 dark:divide-zinc-800">
{filteredEmployees.map((emp) => (
<tr key={emp.id} className="group hover:bg-slate-50/50 dark:hover:bg-zinc-800/20 transition-all duration-500">
{/* Personel */}
<td className="whitespace-nowrap py-6 pl-8 pr-3">
<div className="flex items-center gap-4">
{emp.photo_url ? (
<img src={emp.photo_url} alt="" className="w-14 h-14 rounded-2xl object-cover shadow-lg shadow-blue-900/10 group-hover:scale-110 transition-transform duration-500" />
) : (
<div className="w-14 h-14 rounded-2xl bg-[#173363] flex items-center justify-center text-white font-black text-base shadow-lg shadow-blue-900/10 group-hover:bg-[#CE0515] transition-colors duration-500">
{emp.first_name?.[0]}{emp.last_name?.[0]}
</div>
)}
<div>
<p className="text-sm font-black text-[#173363] dark:text-white group-hover:text-black dark:group-hover:text-white transition-colors">
{emp.first_name} {emp.last_name}
</p>
<div className="flex items-center gap-1.5 mt-1 text-slate-400">
<IdentificationIcon className="w-3.5 h-3.5" />
<p className="text-[10px] font-black tracking-widest">{emp.tc_no || '--'}</p>
</div>
</div>
</div>
</td>
{/* Kurumsal */}
<td className="whitespace-nowrap px-3 py-6">
<p className="text-sm font-black text-slate-700 dark:text-white">{emp.companies?.name}</p>
<div className="flex flex-col gap-1 mt-1.5">
<span className="inline-flex items-center text-[10px] text-[#CE0515] font-black uppercase tracking-widest bg-red-50 dark:bg-red-900/20 px-2 py-0.5 rounded-md w-fit">
{emp.roles?.description}
</span>
<span className="text-[10px] text-slate-400 font-bold">
{emp.employment_types?.name || '-'}
</span>
</div>
</td>
{/* Bölüm / Görev */}
<td className="whitespace-nowrap px-3 py-6">
<div className="flex items-start gap-2">
<BuildingOffice2Icon className="w-4 h-4 text-slate-300 mt-0.5" />
<div>
<p className="text-sm font-black text-slate-700 dark:text-slate-300">{emp.departments?.name || '-'}</p>
<p className="text-[10px] text-slate-400 font-bold mt-1">
{emp.sections?.name ? `${emp.sections.name} / ` : ''}{emp.job_titles?.name || '-'}
</p>
</div>
</div>
</td>
{/* İletişim */}
<td className="whitespace-nowrap px-3 py-6">
<div className="space-y-1.5">
<div className="flex items-center gap-2 text-slate-500 dark:text-slate-400">
<EnvelopeIcon className="w-3.5 h-3.5" />
<p className="text-[11px] font-bold">{emp.email || '-'}</p>
</div>
<div className="flex items-center gap-2 text-slate-500 dark:text-slate-400">
<PhoneIcon className="w-3.5 h-3.5" />
<p className="text-[11px] font-bold">{emp.phone1 || '-'}</p>
</div>
</div>
</td>
{/* Tarihler */}
<td className="whitespace-nowrap px-3 py-6">
<div className="space-y-1.5">
<div className="flex items-center gap-2 text-slate-400">
<CalendarDaysIcon className="w-3.5 h-3.5 text-emerald-500" />
<p className="text-[10px] font-black">{formatDate(emp.start_date)}</p>
</div>
{emp.leave_date && (
<div className="flex items-center gap-2 text-slate-400">
<CalendarDaysIcon className="w-3.5 h-3.5 text-rose-500" />
<p className="text-[10px] font-black">{formatDate(emp.leave_date)}</p>
</div>
)}
</div>
</td>
{/* Durum */}
<td className="whitespace-nowrap px-3 py-6">
<span className={`inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-[0.15em] border ${
emp.status === 'active'
? 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 border-emerald-100 dark:border-emerald-900/30'
: 'bg-slate-50 dark:bg-zinc-800 text-slate-500 dark:text-slate-400 border-slate-100 dark:border-zinc-700'
}`}>
<span className={`w-2 h-2 rounded-full ${emp.status === 'active' ? 'bg-emerald-500 animate-pulse' : 'bg-slate-400'}`} />
{emp.status === 'active' ? 'Aktif' : 'Ayrılan'}
</span>
</td>
{/* İşlemler */}
<td className="whitespace-nowrap py-6 pl-3 pr-8 text-right">
<div className="flex items-center justify-end gap-2 lg:gap-3 lg:translate-x-4 lg:opacity-0 lg:group-hover:translate-x-0 lg:group-hover:opacity-100 transition-all duration-500">
{!emp.user_id && emp.email && (
<button
onClick={() => setIsUserModalOpen(true)}
className="p-2 sm:p-3 rounded-xl sm:rounded-2xl text-emerald-600 hover:text-white hover:bg-emerald-600 transition-all"
>
<KeyIcon className="w-5 h-5" />
</button>
)}
<button
onClick={() => openEditModal(emp)}
className="p-2 sm:p-3 rounded-xl sm:rounded-2xl text-slate-400 hover:text-white hover:bg-[#173363] transition-all"
>
<PencilSquareIcon className="w-5 h-5" />
</button>
<button
onClick={() => handleDelete(emp.id)}
className="p-2 sm:p-3 rounded-xl sm:rounded-2xl text-slate-400 hover:text-white hover:bg-[#CE0515] transition-all"
>
<TrashIcon className="w-5 h-5" />
</button>
</div>
</td>
</tr>
))}
{filteredEmployees.length === 0 && (
<tr>
<td colSpan={7} className="py-20 text-center">
<UsersIcon className="mx-auto h-16 w-16 text-slate-200 dark:text-zinc-800 mb-4" />
<p className="text-slate-500 dark:text-slate-400 font-bold">Herhangi bir personel kaydı bulunamadı.</p>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
{/* Modals */}
<EmployeeModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
companies={companies}
roles={roles}
departments={departments}
sections={sections}
employmentTypes={employmentTypes}
jobTitles={jobTitles}
editingEmployee={editingEmployee}
/>
<UserCreationModal
isOpen={isUserModalOpen}
onClose={() => setIsUserModalOpen(false)}
employeesWithoutUser={employeesWithoutUser}
/>
</div>
);
}