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

331 lines
15 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, useRef } from 'react';
import { XMarkIcon, PhotoIcon } from '@heroicons/react/24/outline';
import { addEmployee, updateEmployee } from '@/app/employees/actions';
interface EmployeeModalProps {
isOpen: boolean;
onClose: () => void;
companies: any[];
roles: any[];
departments: any[];
sections: any[];
employmentTypes: any[];
jobTitles: any[];
editingEmployee?: any;
}
export default function EmployeeModal({
isOpen,
onClose,
companies,
roles,
departments,
sections,
employmentTypes,
jobTitles,
editingEmployee
}: EmployeeModalProps) {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [activeTab, setActiveTab] = useState('personal');
const [selectedDept, setSelectedDept] = useState(editingEmployee?.department_id || '');
const [previewUrl, setPreviewUrl] = useState<string | null>(editingEmployee?.photo_url || null);
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (editingEmployee) {
setSelectedDept(editingEmployee.department_id || '');
setPreviewUrl(editingEmployee.photo_url || null);
} else {
setSelectedDept('');
setPreviewUrl(null);
}
setActiveTab('personal');
}, [editingEmployee, isOpen]);
if (!isOpen) return null;
const filteredSections = sections.filter(s => s.department_id === selectedDept);
const handlePhotoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const url = URL.createObjectURL(file);
setPreviewUrl(url);
}
};
async function handleSubmit(formData: FormData) {
setLoading(true);
setError(null);
try {
let result;
if (editingEmployee) {
result = await updateEmployee(editingEmployee.id, formData);
} else {
result = await addEmployee(formData);
}
if (result.error) {
setError(result.error);
} else {
onClose();
}
} catch (err) {
setError('Bir hata oluştu, lütfen tekrar deneyin.');
} finally {
setLoading(false);
}
}
const inputClass = "w-full rounded-2xl border-0 py-3 px-4 text-slate-900 dark:text-white bg-slate-50 dark:bg-zinc-800 ring-1 ring-inset ring-slate-200 dark:ring-zinc-700 focus:ring-2 focus:ring-[#173363] outline-none transition-all placeholder:text-slate-400 text-sm";
const labelClass = "block text-[10px] font-black uppercase tracking-widest text-slate-400 mb-1.5 ml-1";
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-0 sm:p-4 bg-slate-900/60 backdrop-blur-md transition-all">
<div className="bg-white dark:bg-zinc-900 w-full sm:max-w-4xl h-full sm:h-auto sm:max-h-[90vh] rounded-none sm:rounded-[2.5rem] shadow-2xl border border-slate-100 dark:border-zinc-800 overflow-hidden flex flex-col animate-in fade-in zoom-in duration-300">
{/* Header */}
<div className="px-6 sm:px-10 py-6 sm:py-8 border-b border-slate-50 dark:border-zinc-800 flex items-center justify-between bg-white dark:bg-zinc-900">
<div>
<h2 className="text-xl sm:text-2xl font-black text-[#173363] dark:text-white tracking-tight">
{editingEmployee ? 'Personel Düzenle' : 'Yeni Personel Ekle'}
</h2>
<p className="text-[10px] sm:text-xs font-bold text-slate-400 mt-1">
Personel bilgilerini detaylı olarak yönetin.
</p>
</div>
<button
onClick={onClose}
className="p-2 sm:p-3 rounded-2xl hover:bg-slate-50 dark:hover:bg-zinc-800 text-slate-300 hover:text-[#CE0515] transition-all"
>
<XMarkIcon className="w-5 h-5 sm:w-6 sm:h-6" />
</button>
</div>
{/* Tabs */}
<div className="px-6 sm:px-10 flex gap-4 sm:gap-8 border-b border-slate-50 dark:border-zinc-800 bg-white dark:bg-zinc-900 overflow-x-auto no-scrollbar">
{[
{ id: 'personal', label: 'KİŞİSEL' },
{ id: 'employment', label: 'ÇALIŞMA' },
{ id: 'contact', label: 'İLETİŞİM' }
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`py-4 sm:py-6 text-[10px] sm:text-[11px] font-black tracking-[0.15em] sm:tracking-[0.2em] transition-all border-b-2 whitespace-nowrap ${
activeTab === tab.id
? 'border-[#CE0515] text-[#CE0515]'
: 'border-transparent text-slate-400 hover:text-slate-600'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Body */}
<form action={handleSubmit} className="flex-1 overflow-y-auto p-6 sm:p-10 custom-scrollbar">
{error && (
<div className="mb-6 p-4 bg-rose-50 border border-rose-100 rounded-2xl text-rose-600 text-[10px] sm:text-xs font-black text-center uppercase tracking-widest leading-relaxed">
{error}
</div>
)}
<div className={activeTab === 'personal' ? 'block' : 'hidden'}>
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex flex-col md:flex-row gap-10">
{/* Photo Upload */}
<div className="flex-shrink-0 flex flex-col items-center gap-4">
<div
onClick={() => fileInputRef.current?.click()}
className="w-40 h-40 rounded-[2.5rem] bg-slate-50 border-2 border-dashed border-slate-200 flex flex-col items-center justify-center cursor-pointer hover:border-[#173363] hover:bg-slate-100 transition-all overflow-hidden group"
>
{previewUrl ? (
<img src={previewUrl} alt="Preview" className="w-full h-full object-cover" />
) : (
<>
<PhotoIcon className="w-10 h-10 text-slate-300 group-hover:text-[#173363] transition-colors" />
<span className="text-[10px] font-black text-slate-400 mt-2 px-4 text-center">FOTOĞRAF YÜKLE</span>
</>
)}
</div>
<input
type="file"
name="photo"
ref={fileInputRef}
onChange={handlePhotoChange}
className="hidden"
accept="image/*"
/>
<input type="hidden" name="existing_photo_url" value={editingEmployee?.photo_url || ''} />
</div>
<div className="flex-1 grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className={labelClass}>AD</label>
<input type="text" name="first_name" defaultValue={editingEmployee?.first_name} required className={inputClass} />
</div>
<div>
<label className={labelClass}>SOYAD</label>
<input type="text" name="last_name" defaultValue={editingEmployee?.last_name} required className={inputClass} />
</div>
<div>
<label className={labelClass}>T.C. KİMLİK NO</label>
<input type="text" name="tc_no" defaultValue={editingEmployee?.tc_no} className={inputClass} maxLength={11} />
</div>
<div>
<label className={labelClass}>CİNSİYET</label>
<select name="gender" defaultValue={editingEmployee?.gender} className={inputClass}>
<option value="">Seçiniz</option>
<option value="Erkek">Erkek</option>
<option value="Kadın">Kadın</option>
</select>
</div>
<div>
<label className={labelClass}>DOĞUM TARİHİ</label>
<input type="date" name="birth_date" defaultValue={editingEmployee?.birth_date} className={inputClass} />
</div>
<div>
<label className={labelClass}>DOĞUM YERİ</label>
<input type="text" name="birth_place" defaultValue={editingEmployee?.birth_place} className={inputClass} />
</div>
</div>
</div>
</div>
</div>
<div className={activeTab === 'employment' ? 'block' : 'hidden'}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div>
<label className={labelClass}>ŞİRKET</label>
<select name="company_id" defaultValue={editingEmployee?.company_id} required className={inputClass}>
<option value="">Seçiniz</option>
{companies.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
</select>
</div>
<div>
<label className={labelClass}>SİSTEM ROLÜ</label>
<select name="role_id" defaultValue={editingEmployee?.role_id} required className={inputClass}>
<option value="">Seçiniz</option>
{roles.map(r => <option key={r.id} value={r.id}>{r.description}</option>)}
</select>
</div>
<div>
<label className={labelClass}>DEPARTMAN</label>
<select
name="department_id"
value={selectedDept}
onChange={(e) => setSelectedDept(e.target.value)}
className={inputClass}
>
<option value="">Seçiniz</option>
{departments.map(d => <option key={d.id} value={d.id}>{d.name}</option>)}
</select>
</div>
<div>
<label className={labelClass}>BÖLÜM</label>
<select name="section_id" defaultValue={editingEmployee?.section_id} className={inputClass}>
<option value="">Seçiniz</option>
{filteredSections.map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
</select>
</div>
<div>
<label className={labelClass}>GÖREV / ÜNVAN</label>
<select name="job_title_id" defaultValue={editingEmployee?.job_title_id} className={inputClass}>
<option value="">Seçiniz</option>
{jobTitles.map(jt => <option key={jt.id} value={jt.id}>{jt.name}</option>)}
</select>
</div>
<div>
<label className={labelClass}>ÇALIŞMA ŞEKLİ</label>
<select name="employment_type_id" defaultValue={editingEmployee?.employment_type_id} className={inputClass}>
<option value="">Seçiniz</option>
{employmentTypes.map(et => <option key={et.id} value={et.id}>{et.name}</option>)}
</select>
</div>
<div>
<label className={labelClass}>İŞE BAŞLAMA TARİHİ</label>
<input type="date" name="start_date" defaultValue={editingEmployee?.start_date} className={inputClass} />
</div>
<div>
<label className={labelClass}>İŞTEN AYRILIŞ TARİHİ</label>
<input type="date" name="leave_date" defaultValue={editingEmployee?.leave_date} className={inputClass} />
</div>
<div>
<label className={labelClass}>SİSTEM DURUMU</label>
<select name="status" defaultValue={editingEmployee?.status || 'active'} className={inputClass}>
<option value="active">Aktif</option>
<option value="inactive">Pasif</option>
<option value="terminated">İlişiği Kesildi</option>
</select>
</div>
</div>
</div>
<div className={activeTab === 'contact' ? 'block' : 'hidden'}>
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<label className={labelClass}>E-POSTA</label>
<input type="email" name="email" defaultValue={editingEmployee?.email} className={inputClass} placeholder="ornek@abisena.com" />
</div>
<div>
<label className={labelClass}>TELEFON 1</label>
<input type="tel" name="phone1" defaultValue={editingEmployee?.phone1} className={inputClass} placeholder="05XX XXX XX XX" />
</div>
<div>
<label className={labelClass}>TELEFON 2</label>
<input type="tel" name="phone2" defaultValue={editingEmployee?.phone2} className={inputClass} placeholder="05XX XXX XX XX" />
</div>
<div>
<label className={labelClass}>ASKERLİK DURUMU</label>
<select name="military_status" defaultValue={editingEmployee?.military_status} className={inputClass}>
<option value="">Seçiniz</option>
<option value="Yapıldı">Yapıldı</option>
<option value="Muaf">Muaf</option>
<option value="Tecilli">Tecilli</option>
</select>
</div>
</div>
<div>
<label className={labelClass}>ADRES</label>
<textarea name="address" defaultValue={editingEmployee?.address} rows={3} className={inputClass + " resize-none"} />
</div>
<div className="flex items-center gap-4 bg-slate-50 p-6 rounded-2xl border border-slate-200">
<input
type="checkbox"
name="has_driving_license"
defaultChecked={editingEmployee?.has_driving_license}
className="w-5 h-5 rounded-lg border-slate-300 text-[#173363] focus:ring-[#173363]"
/>
<label className="text-sm font-bold text-[#173363]">SÜRÜCÜ EHLİYETİ VAR</label>
</div>
</div>
</div>
<div className="mt-8 sm:mt-12 flex flex-col sm:flex-row gap-3 sm:gap-4 pb-4">
<button
type="button"
onClick={onClose}
className="px-8 py-4 sm:py-5 rounded-full font-black text-slate-300 hover:bg-slate-50 transition-all text-[10px] sm:text-[11px] uppercase tracking-[0.2em]"
>
Vazgeç
</button>
<button
type="submit"
disabled={loading}
className="bg-[#173363] hover:bg-[#CE0515] text-white px-10 py-4 sm:py-5 rounded-full font-black shadow-xl shadow-blue-900/10 transition-all active:scale-95 disabled:opacity-50 text-[10px] sm:text-[11px] uppercase tracking-[0.2em]"
>
{loading ? 'YÜKLENİYOR...' : (editingEmployee ? 'BİLGİLERİ GÜNCELLE' : 'PERSONELİ KAYDET')}
</button>
</div>
</form>
</div>
</div>
);
}