diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..672cf13 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..dbe0d62 Binary files /dev/null and b/public/logo.png differ diff --git a/src/app/companies/page.tsx b/src/app/companies/page.tsx index d0a5d92..ef206e8 100644 --- a/src/app/companies/page.tsx +++ b/src/app/companies/page.tsx @@ -13,15 +13,17 @@ export default async function CompaniesPage() { return (
-
+
-

- - Şirket Yönetimi -

-

- Sisteme kayıtlı şirketlerin listesi ve yeni şirket ekleme alanı. -

+
+
+
+

Şirket Yönetimi

+

+ Sisteme kayıtlı şirketlerin listesi ve yeni şirket ekleme alanı. +

+
+
@@ -34,26 +36,32 @@ export default async function CompaniesPage() { Yeni Şirket Ekle
-

Çalışanları atayabilmek için öncelikle şirket kaydı açmalısınız.

+

Personelleri atayabilmek için öncelikle şirket kaydı açmalısınız.

-
+ { + 'use server'; + await addCompany(formData); + }} + className="mt-5 space-y-4" + >
- +
@@ -62,13 +70,13 @@ export default async function CompaniesPage() { {/* Company List */}
- - +
+ - - - + {companies?.map((company) => ( - - + - - diff --git a/src/app/employees/actions.ts b/src/app/employees/actions.ts index 90577d6..75ac06d 100644 --- a/src/app/employees/actions.ts +++ b/src/app/employees/actions.ts @@ -11,9 +11,10 @@ export async function addEmployee(formData: FormData) { const lastName = formData.get('last_name') as string const email = formData.get('email') as string const companyId = formData.get('company_id') as string + const roleId = formData.get('role_id') as string - if (!email || !companyId) { - return { error: 'E-posta ve Şirket seçimi zorunludur.' } + if (!email || !companyId || !roleId) { + return { error: 'E-posta, Şirket ve Rol seçimi zorunludur.' } } // Admin creating users manually currently requires an admin API setup or the user registering themselves @@ -39,9 +40,10 @@ export async function addEmployee(formData: FormData) { .insert([{ user_id: existingUser.id, company_id: companyId, + role_id: roleId, department: formData.get('department'), title: formData.get('title'), - status: 'active' + status: formData.get('status') || 'active' }]) if (employeeError) { @@ -49,7 +51,7 @@ export async function addEmployee(formData: FormData) { if (employeeError.code === '23505') { return { error: 'Bu kullanıcı zaten bu şirkete eklenmiş.' } } - return { error: 'Çalışan eklenirken hata: ' + employeeError.message } + return { error: 'Personel eklenirken hata: ' + employeeError.message } } revalidatePath('/employees') @@ -65,7 +67,29 @@ export async function deleteEmployee(id: string) { .eq('id', id) if (error) { - return { error: 'Çalışan silinirken hata oluştu: ' + error.message } + return { error: 'Personel silinirken hata oluştu: ' + error.message } + } + + revalidatePath('/employees') + return { success: true } +} + +export async function updateEmployee(id: string, formData: FormData) { + const supabase = await createClient() + + const { error } = await supabase + .from('employees') + .update({ + company_id: formData.get('company_id'), + role_id: formData.get('role_id'), + department: formData.get('department'), + title: formData.get('title'), + status: formData.get('status') + }) + .eq('id', id) + + if (error) { + return { error: 'Personel güncellenirken hata oluştu: ' + error.message } } revalidatePath('/employees') diff --git a/src/app/employees/page.tsx b/src/app/employees/page.tsx index 4a8ba7d..d749c55 100644 --- a/src/app/employees/page.tsx +++ b/src/app/employees/page.tsx @@ -1,6 +1,5 @@ import { createClient } from '@/utils/supabase/server' -import { addEmployee, deleteEmployee } from './actions' -import { UsersIcon, TrashIcon } from '@heroicons/react/24/outline' +import EmployeeTable from '@/components/employees/EmployeeTable' export default async function EmployeesPage() { const supabase = await createClient() @@ -10,180 +9,39 @@ export default async function EmployeesPage() { .from('employees') .select(` id, + user_id, + company_id, + role_id, department, title, status, created_at, - companies ( name ), - users ( first_name, last_name, email ), - roles ( name, description ) + companies ( id, name ), + users ( id, first_name, last_name, email ), + roles ( id, name, description ) `) .order('created_at', { ascending: false }) - // Fetch companies for the dropdown + // Fetch companies for the modal const { data: companies } = await supabase .from('companies') .select('id, name') .order('name') + // Fetch roles for the modal + const { data: roles } = await supabase + .from('roles') + .select('id, name, description') + .order('name') + return (
-
-
-

- - Çalışan Yönetimi -

-

- Sisteme kayıtlı çalışanları görüntüleyin ve yeni çalışanları şirketlere atayın. -

-
-
- -
- - {/* Add Employee Form */} -
-
-

- Yeni Çalışan Ata -

-
-

Sisteme daha önce kayıt olmuş (şifre oluşturmuş) kullanıcıları şirketlere atayın.

-
- -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- - - -
-
- - {/* Employee List */} -
-
-
+ Şirket Adı + Kayıt Tarihi @@ -76,13 +84,13 @@ export default async function CompaniesPage() {
+
{company.name} + {new Date(company.created_at).toLocaleDateString('tr-TR')} @@ -90,7 +98,7 @@ export default async function CompaniesPage() { 'use server'; await deleteCompany(company.id) }}> - @@ -99,7 +107,7 @@ export default async function CompaniesPage() { ))} {(!companies || companies.length === 0) && (
+ Henüz kayıtlı şirket bulunmamaktadır.
- - - - - - - - - - - {employees?.map((emp) => ( - - - - - - - - ))} - {(!employees || employees.length === 0) && ( - - - - )} - -
- Kişi - - Şirket - - Departman / Ünvan - - Durum - - İşlemler -
-
- {emp.users?.first_name} {emp.users?.last_name} -
-
{emp.users?.email}
-
-
{emp.companies?.name}
-
-
{emp.department || '-'}
-
{emp.title || '-'}
-
- - {emp.status === 'active' ? 'Aktif' : 'Pasif'} - - -
{ - 'use server'; - await deleteEmployee(emp.id) - }}> - -
-
- Sisteme kayıtlı çalışan bulunmamaktadır. -
-
-
- -
+
) } + diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 939bbf6..65b75e3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -16,10 +16,14 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "HRMS Yönetim Sistemi", + title: "Abisena | Personel Takip Sistemi", description: "Şirket içi insan kaynakları yönetim paneli", + icons: { + icon: "/favicon.png", + }, }; + export default async function RootLayout({ children, }: Readonly<{ diff --git a/src/app/leave-requests/actions.ts b/src/app/leave-requests/actions.ts index eaf5cfb..10fa477 100644 --- a/src/app/leave-requests/actions.ts +++ b/src/app/leave-requests/actions.ts @@ -27,7 +27,7 @@ export async function submitLeaveRequest(formData: FormData) { .single() if (empError || !employeeData) { - return { error: 'Seçili şirket için çalışan kaydınız bulunamadı.' } + return { error: 'Seçili şirket için personel kaydınız bulunamadı.' } } const { error } = await supabase diff --git a/src/app/leave-requests/page.tsx b/src/app/leave-requests/page.tsx index 6231d83..633e71d 100644 --- a/src/app/leave-requests/page.tsx +++ b/src/app/leave-requests/page.tsx @@ -53,17 +53,19 @@ export default async function LeaveRequestsPage() { return (
-
+
-

- - İzin Talepleri -

-

- {isAdminOrManager - ? 'Tüm çalışanların izin taleplerini görüntüleyin ve onaylayın.' - : 'Kendi izin taleplerinizi oluşturun ve durumlarını takip edin.'} -

+
+
+
+

İzin Talepleri

+

+ {isAdminOrManager + ? 'Tüm personellerin izin taleplerini görüntüleyin ve onaylayın.' + : 'Kendi izin taleplerinizi oluşturun ve durumlarını takip edin.'} +

+
+
@@ -86,7 +88,7 @@ export default async function LeaveRequestsPage() { id="company_id" name="company_id" required - className="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-zinc-800 dark:text-white dark:ring-zinc-700" + className="mt-2 block w-full rounded-xl border-0 py-2.5 pl-3 pr-10 text-slate-900 ring-1 ring-inset ring-slate-200 dark:ring-zinc-700 dark:bg-zinc-800 dark:text-white focus:ring-2 focus:ring-indigo-500 sm:text-sm outline-none" > {userCompanies?.filter((c: any) => c.companies).map((c: any) => ( @@ -123,7 +125,7 @@ export default async function LeaveRequestsPage() { id="reason" rows={3} required - className="mt-2 block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-zinc-700 dark:bg-zinc-800 dark:text-white sm:text-sm sm:leading-6" + className="mt-2 block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 shadow-sm ring-1 ring-inset ring-slate-200 dark:ring-zinc-700 dark:bg-zinc-800 dark:text-white placeholder:text-slate-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 sm:text-sm outline-none" />
@@ -134,7 +136,7 @@ export default async function LeaveRequestsPage() { const { submitLeaveRequest } = await import('./actions'); await submitLeaveRequest(formData); }} - className="inline-flex w-full items-center justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500" + className="inline-flex w-full items-center justify-center rounded-2xl bg-indigo-600 px-3 py-3 text-sm font-bold text-white shadow-sm shadow-indigo-200 dark:shadow-none hover:bg-indigo-700 transition-all active:scale-95" > Talep Oluştur @@ -151,18 +153,18 @@ export default async function LeaveRequestsPage() {
- + - - - - {isAdminOrManager && ( @@ -172,30 +174,32 @@ export default async function LeaveRequestsPage() { )} - + {requests?.map((req: any) => { - const employeeInfo = req.employees?.users || {}; + const employeeData = Array.isArray(req.employees) ? req.employees[0] : req.employees; + const userData = Array.isArray(employeeData?.users) ? employeeData.users[0] : employeeData?.users; + const companyData = Array.isArray(employeeData?.companies) ? employeeData.companies[0] : employeeData?.companies; return ( - + - - - diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index e5b4346..ca86658 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -6,77 +6,68 @@ export default function LoginPage({ searchParams: { error?: string } }) { return ( -
-
+
+
-

- HRMS Sistemine Giriş +

+ Abisena Giriş

-

- Lütfen e-posta ve şifrenizle giriş yapın. +

+ Personel Takip Sistemine Giriş Yapın

-
+ {searchParams?.error && ( -
+
E-posta adresi veya şifre hatalı.
)} -
+
-
- -
+
-
-
- -
-
- -
-
- -
- + Şifre + +
- + +
) diff --git a/src/app/page.tsx b/src/app/page.tsx index ae23c51..42973bb 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -11,18 +11,26 @@ export default async function Home() { redirect('/login') } - // Fetch the extended user metadata from our 'users' table - const { data: dbUser } = await supabase + // Deep debug: Fetch users and employees separately + const { data: dbUser, error: dbUserError } = await supabase .from('users') - .select('*, roles(name, description)') + .select('*') .eq('id', user.id) .single() - const roleData: any = dbUser?.roles - const role = Array.isArray(roleData) ? roleData[0] : roleData + const { data: dbEmployees, error: dbEmpError } = await supabase + .from('employees') + .select('*, roles(*)') + .eq('user_id', user.id) + + const userData = dbUser + const role = dbEmployees?.[0]?.roles || null + const displayName = userData?.first_name + ? `${userData.first_name}${userData.last_name ? ' ' + userData.last_name : ''}` + : user.email?.split('@')[0] const stats = [ - { name: 'Toplam Çalışan', stat: '0', icon: UsersIcon, change: '12%', changeType: 'increase' }, + { name: 'Toplam Personel', stat: '0', icon: UsersIcon, change: '12%', changeType: 'increase' }, { name: 'Bekleyen İzinler', stat: '0', icon: CalendarDaysIcon, change: '3', changeType: 'increase' }, { name: 'Aktif Şirketler', stat: '1', icon: BuildingOfficeIcon, change: '0%', changeType: 'none' }, ] @@ -30,75 +38,92 @@ export default async function Home() { return (
- {/* Welcome Section - Premium Header */} -
-
-

- Hoş Geldiniz, {dbUser?.first_name || user.email?.split('@')[0]} 👋 -

-

- IK Yönetim Sistemine tekrar hoş geldiniz. Bugün {role?.name || 'Kullanıcı'} yetkileriyle sisteme bağlısınız. -

+ {/* Welcome Section - Modern & Clean */} +
+
+
+

+ Hoş Geldiniz, {displayName} 👋 +

+

+ IK Yönetim Sistemine tekrar hoş geldiniz. Bugün sisteme + + {role?.name || 'Kullanıcı'} + + yetkileriyle bağlısınız. +

+
+
+ {[1,2,3,4].map(i => ( +
+ ))} +
+12
+
- {/* Decorative backdrop blobs */} -
-
+ + {/* Corporate decorative element */} +
+
- {/* Stats Grid */} -
+ {/* Stats Grid - abisena.tr Style */} +
{stats.map((item) => (
-
-
-
+ {/* Subtle progress bar */} +
+
- {/* Subtle background icon for premium feel */} -
))}
{/* Profile & Quick Actions section */}
-
-

-
+
+

+
Sistem Bilgileri

-
    -
  • - E-posta - {user.email} +
      +
    • + E-posta Adresi + {user.email}
    • -
    • - Hesap Tipi - -
      +
    • + Hesap Yetkisi + {role?.description || 'Standart Erişim'}
    • - Son Giriş - {new Date().toLocaleDateString('tr-TR')} + Sistem Tarihi + {new Date().toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric' })}
+ + +

); } diff --git a/src/components/employees/EmployeeModal.tsx b/src/components/employees/EmployeeModal.tsx new file mode 100644 index 0000000..909659b --- /dev/null +++ b/src/components/employees/EmployeeModal.tsx @@ -0,0 +1,175 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { XMarkIcon } from '@heroicons/react/24/outline'; +import { addEmployee, updateEmployee } from '@/app/employees/actions'; + +interface EmployeeModalProps { + isOpen: boolean; + onClose: () => void; + companies: any[]; + roles: any[]; + editingEmployee?: any; +} + +export default function EmployeeModal({ isOpen, onClose, companies, roles, editingEmployee }: EmployeeModalProps) { + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + if (!isOpen) return null; + + 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); + } + } + + return ( +
+
+ + {/* Header */} +
+
+

+ {editingEmployee ? 'Personel Düzenle' : 'Yeni Personel Ekle'} +

+

+ Personel bilgilerini eksiksiz doldurunuz. +

+
+ +
+ + {/* Body */} +
+ {error && ( +
+ {error} +
+ )} + +
+ {!editingEmployee && ( +
+ + +
+ )} + +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+ +
+
+ ); +} diff --git a/src/components/employees/EmployeeTable.tsx b/src/components/employees/EmployeeTable.tsx new file mode 100644 index 0000000..8bd117d --- /dev/null +++ b/src/components/employees/EmployeeTable.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { useState } from 'react'; +import { + UsersIcon, + TrashIcon, + PencilSquareIcon, + PlusIcon, + MagnifyingGlassIcon +} from '@heroicons/react/24/outline'; +import { deleteEmployee } from '@/app/employees/actions'; +import EmployeeModal from '@/components/employees/EmployeeModal'; + +interface EmployeeTableProps { + initialEmployees: any[]; + companies: any[]; + roles: any[]; +} + +export default function EmployeeTable({ initialEmployees, companies, roles }: EmployeeTableProps) { + const [employees, setEmployees] = useState(initialEmployees); + const [searchTerm, setSearchTerm] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [isModalOpen, setIsModalOpen] = useState(false); + const [editingEmployee, setEditingEmployee] = useState(null); + + const filteredEmployees = employees.filter(emp => { + const userData = Array.isArray(emp.users) ? emp.users[0] : emp.users; + const fullName = `${userData?.first_name || ''} ${userData?.last_name || ''}`.toLowerCase(); + const email = (userData?.email || '').toLowerCase(); + const matchesSearch = fullName.includes(searchTerm.toLowerCase()) || email.includes(searchTerm.toLowerCase()); + const matchesStatus = statusFilter === 'all' || emp.status === statusFilter; + return matchesSearch && matchesStatus; + }); + + const handleEdit = (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); + } + } + }; + + return ( +
+ {/* Header with Add Button */} +
+
+
+
+
+

Personeller

+

+ Şirketinizdeki tüm personel kayıtlarını yönetin +

+
+
+
+ +
+ + {/* Filter & Search Bar */} +
+
+ + setSearchTerm(e.target.value)} + placeholder="İsim veya e-posta ile ara..." + className="w-full pl-12 pr-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-medium focus:ring-2 focus:ring-[#CE0515] transition-all outline-none placeholder:text-slate-400" + /> +
+
+ + + +
+
+ + {/* Table Section */} +
+
+
- Çalışan + + Personel + Tarih Aralığı + Gerekçe + Durum
-
- {employeeInfo?.first_name} {employeeInfo?.last_name} +
+ {userData?.first_name} {userData?.last_name}
-
{req.employees?.companies?.name}
+
{companyData?.name}
+
{new Date(req.start_date).toLocaleDateString('tr-TR')}
{new Date(req.end_date).toLocaleDateString('tr-TR')}
+ {req.reason} - {req.status === 'approved' ? 'Onaylandı' : req.status === 'rejected' ? 'Reddedildi' : 'Bekliyor'} @@ -209,7 +213,7 @@ export default async function LeaveRequestsPage() { 'use server'; await updateLeaveStatus(req.id, 'approved') }} - className="text-green-600 hover:text-green-900 dark:text-green-500 dark:hover:text-green-400 bg-green-50 hover:bg-green-100 dark:bg-green-500/10 p-2 rounded-lg transition-colors" title="Onayla"> + className="text-emerald-600 hover:text-emerald-900 dark:text-emerald-500 dark:hover:text-emerald-400 p-2 rounded-xl hover:bg-emerald-50 dark:hover:bg-emerald-500/10 transition-all" title="Onayla"> @@ -228,7 +232,7 @@ export default async function LeaveRequestsPage() { )})} {(!requests || requests.length === 0) && (
+ Henüz bir izin talebi bulunmamaktadır.
+ + + + + + + + + + + {filteredEmployees.map((emp) => { + const userData = Array.isArray(emp.users) ? emp.users[0] : emp.users; + const companyData = Array.isArray(emp.companies) ? emp.companies[0] : emp.companies; + const roleData = Array.isArray(emp.roles) ? emp.roles[0] : emp.roles; + + return ( + + + + + + + + ); + })} + {filteredEmployees.length === 0 && ( + + + + )} + +
PersonelŞirket / RolDepartman / ÜnvanDurum + İşlemler +
+
+
+ {userData?.first_name?.[0]}{userData?.last_name?.[0]} +
+
+

+ {userData?.first_name} {userData?.last_name} +

+

{userData?.email}

+
+
+
+

{companyData?.name}

+

{roleData?.description}

+
+

{emp.department || '-'}

+

{emp.title || '-'}

+
+ + + {emp.status === 'active' ? 'Aktif' : 'Pasif'} + + +
+ + +
+
+ +

Herhangi bir personel kaydı bulunamadı.

+
+
+
+ + {/* Modal */} + setIsModalOpen(false)} + companies={companies} + roles={roles} + editingEmployee={editingEmployee} + /> +
+ ); +} diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index c068de4..c0f741f 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -11,9 +11,12 @@ export function Header({ user }: { user?: { email?: string; first_name?: string
Profil -
+
{user?.first_name?.[0]?.toUpperCase() || user?.email?.[0]?.toUpperCase() || 'U'}
+ + + {user?.first_name || user?.email?.split('@')[0] || 'Kullanıcı'} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 4cbdb08..cde7809 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import Link from 'next/link' +import Image from 'next/image' import { usePathname } from 'next/navigation' import { HomeIcon, @@ -16,7 +17,7 @@ import { const navigation = [ { name: 'Dashboard', href: '/', icon: HomeIcon }, - { name: 'Çalışanlar', href: '/employees', icon: UsersIcon }, + { name: 'Personeller', href: '/employees', icon: UsersIcon }, { name: 'İzinler', href: '/leave-requests', icon: CalendarDaysIcon }, { name: 'Şirketler', href: '/companies', icon: BuildingOfficeIcon }, { name: 'Ayarlar', href: '/settings', icon: Cog6ToothIcon }, @@ -29,16 +30,18 @@ export function Sidebar() { return ( <> {/* Mobile sidebar trigger */} -
+
-
- HRMS +
+ Abisena Logo +
+ Personel
Sistemi
@@ -59,11 +62,11 @@ export function Sidebar() {
{/* Mobile Sidebar Content */} -
-
-
- - HRMS +
+
+
+ Abisena Logo + TAKİP SİSTEMİ