diff --git a/src/app/companies/actions.ts b/src/app/companies/actions.ts index 55f008a..b067632 100644 --- a/src/app/companies/actions.ts +++ b/src/app/companies/actions.ts @@ -6,6 +6,22 @@ import { revalidatePath } from 'next/cache' export async function addCompany(formData: FormData) { const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) return { error: 'Oturum bulunamadı.' } + + // Check if user has admin/manager role somewhere + const { data: employeeRoles } = await supabase + .from('employees') + .select('roles(name)') + .eq('user_id', user.id) + + const canManage = employeeRoles?.some((emp: any) => { + const roleName = Array.isArray(emp.roles) ? emp.roles[0]?.name : emp.roles?.name + return roleName === 'admin' || roleName === 'manager' + }) || false + + if (!canManage) return { error: 'Şirket ekleme yetkiniz bulunmamaktadır.' } + const name = formData.get('name') as string if (!name) return { error: 'Şirket adı zorunludur.' } @@ -23,6 +39,20 @@ export async function addCompany(formData: FormData) { export async function deleteCompany(id: string) { const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) return { error: 'Oturum bulunamadı.' } + + const { data: employeeRoles } = await supabase + .from('employees') + .select('roles(name)') + .eq('user_id', user.id) + + const canManage = employeeRoles?.some((emp: any) => { + const roleName = Array.isArray(emp.roles) ? emp.roles[0]?.name : emp.roles?.name + return roleName === 'admin' || roleName === 'manager' + }) || false + + if (!canManage) return { error: 'Şirket silme yetkiniz bulunmamaktadır.' } const { error } = await supabase .from('companies') @@ -30,7 +60,7 @@ export async function deleteCompany(id: string) { .eq('id', id) if (error) { - return { error: 'Şirket silinirken bir hata oluştu: ' + error.message } + return { error: 'Şirket silünerken bir hata oluştu: ' + error.message } } revalidatePath('/companies') diff --git a/src/app/companies/page.tsx b/src/app/companies/page.tsx index a49db9a..a47b150 100644 --- a/src/app/companies/page.tsx +++ b/src/app/companies/page.tsx @@ -1,11 +1,30 @@ import { createClient } from '@/utils/supabase/server' import { deleteCompany } from './actions' -import { BuildingOfficeIcon, TrashIcon } from '@heroicons/react/24/outline' -import CompanyForm from '@/components/companies/CompanyForm' +import { BuildingOfficeIcon, TrashIcon, PlusIcon } from '@heroicons/react/24/outline' +import { redirect } from 'next/navigation' +import AddCompanyModal from '@/components/companies/AddCompanyModal' export default async function CompaniesPage() { const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) return redirect('/login') + + // Authorization Check + const { data: employeeRoles } = await supabase + .from('employees') + .select('roles(name)') + .eq('user_id', user.id) + + const isAdminOrManager = employeeRoles?.some((emp: any) => { + const roleName = Array.isArray(emp.roles) ? emp.roles[0]?.name : emp.roles?.name + return roleName === 'admin' || roleName === 'manager' + }) || false + + if (!isAdminOrManager) { + return redirect('/') + } + // Fetch companies const { data: companies } = await supabase .from('companies') @@ -13,90 +32,89 @@ export default async function CompaniesPage() { .order('created_at', { ascending: false }) return ( -
-
-
-
-
-
-

Şirket Yönetimi

-

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

-
+
+
+
+
+
+

Şirket Yönetimi

+

+ Sisteme kayıtlı şirketlerin listesi ve yönetim alanı. +

+ + {/* Modal bazlı yeni şirket ekleme */} +
-
- - {/* Add Company Form */} -
-
-

- Yeni Şirket Ekle -

-
-

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

-
- - -
-
- - {/* Company List */} -
-
- - - - - - - - - - {companies?.map((company) => ( - - - + + + ))} + {(!companies || companies.length === 0) && ( + + + + )} + +
- Şirket Adı - - Kayıt Tarihi - - İşlemler -
- {company.name} - +
+
+ + + + + + + + + + {companies?.map((company) => ( + + + - - - ))} - {(!companies || companies.length === 0) && ( - - - - )} - -
+ Şirket Adı + + Kayıt Tarihi + + İşlemler +
+
+
+ +
+ + {company.name} + +
+
+ {new Date(company.created_at).toLocaleDateString('tr-TR')} - -
{ - 'use server'; - const res = await deleteCompany(company.id); - if (res.error) { - alert(res.error); - } - }}> - -
-
- Henüz kayıtlı şirket bulunmamaktadır. -
-
+ +
+
{ + 'use server'; + const res = await deleteCompany(company.id); + if (res.error) { + // Note: In server component we can't easily alert, this needs client-side handling ideally + console.error(res.error); + } + }}> + +
+
+
+ + Henüz kayıtlı şirket bulunmamaktadır. +
+
-
) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 65b75e3..9b017f4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -34,6 +34,7 @@ export default async function RootLayout({ // Fetch db user profile if logged in let dbUser = null + let isAdminSomewhere = false if (user) { const { data } = await supabase .from('users') @@ -41,6 +42,15 @@ export default async function RootLayout({ .eq('id', user.id) .single() dbUser = data + + const { data: employeeRoles } = await supabase + .from('employees') + .select('roles(name)') + .eq('user_id', user.id) + + isAdminSomewhere = employeeRoles?.some((emp: any) => + emp.roles?.name === 'admin' || emp.roles?.name === 'manager' + ) || false } return ( @@ -50,7 +60,7 @@ export default async function RootLayout({ > {user ? (
- +
diff --git a/src/app/leave-requests/actions.ts b/src/app/leave-requests/actions.ts index 10fa477..c24d71b 100644 --- a/src/app/leave-requests/actions.ts +++ b/src/app/leave-requests/actions.ts @@ -12,28 +12,54 @@ export async function submitLeaveRequest(formData: FormData) { const startDate = formData.get('start_date') as string const endDate = formData.get('end_date') as string const reason = formData.get('reason') as string - const companyId = formData.get('company_id') as string // which company they are requesting leave for + const leaveTypeId = formData.get('leave_type_id') as string + const companyId = formData.get('company_id') as string + const targetEmployeeId = formData.get('employee_id') as string // Optional: for admin use - if (!startDate || !endDate || !companyId || !reason) { + if (!startDate || !endDate || !companyId || !reason || !leaveTypeId) { return { error: 'Tüm alanları doldurunuz.' } } - // Find their employee record for this specific company - const { data: employeeData, error: empError } = await supabase + // Find user's admin/manager status for checks + const { data: currentUserEmployee } = await supabase .from('employees') - .select('id') + .select('id, roles(name)') .eq('user_id', user.id) .eq('company_id', companyId) - .single() + .single(); - if (empError || !employeeData) { - return { error: 'Seçili şirket için personel kaydınız bulunamadı.' } + const isManager = (currentUserEmployee?.roles as any)?.name === 'admin' || (currentUserEmployee?.roles as any)?.name === 'manager'; + + let finalEmployeeId = targetEmployeeId; + + if (!isManager || !targetEmployeeId) { + // If not manager or no target ID, use the current user's employee record for this company + if (!currentUserEmployee) { + return { error: 'Seçili şirket için personel kaydınız bulunamadı.' } + } + finalEmployeeId = currentUserEmployee.id; + } + + // Check if this leave type is restricted to admins only + const { data: leaveType, error: typeError } = await supabase + .from('leave_types') + .select('only_admin_can_create') + .eq('id', leaveTypeId) + .single(); + + if (typeError) return { error: 'İzin türü doğrulanamadı.' }; + + if (leaveType.only_admin_can_create) { + if (!isManager) { + return { error: 'Bu izin türü sadece yöneticiler tarafından tanımlanabilir.' }; + } } const { error } = await supabase .from('leave_requests') .insert([{ - employee_id: employeeData.id, + employee_id: finalEmployeeId, + leave_type_id: leaveTypeId, start_date: startDate, end_date: endDate, reason: reason, @@ -50,10 +76,44 @@ export async function submitLeaveRequest(formData: FormData) { export async function updateLeaveStatus(id: string, newStatus: 'approved' | 'rejected') { const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) return { error: 'Oturum bulunamadı.' } + + // 1. Fetch leave request to know its company + const { data: leaveReq, error: fetchError } = await supabase + .from('leave_requests') + .select('employee_id, employees(company_id)') + .eq('id', id) + .single() + + if (fetchError || !leaveReq) return { error: 'İzin talebi bulunamadı.' } + + const companyId = (leaveReq.employees as any)?.company_id + + // 2. Check if current user is admin/manager in that company + const { data: currentUserEmployee } = await supabase + .from('employees') + .select('roles(name)') + .eq('user_id', user.id) + .eq('company_id', companyId) + .single() + + const isManager = (currentUserEmployee?.roles as any)?.name === 'admin' || (currentUserEmployee?.roles as any)?.name === 'manager'; + + if (!isManager) { + return { error: 'Bu izin talebini onaylamak için yetkiniz bulunmamaktadır.' } + } + + const updateData: any = { status: newStatus } + + if (newStatus === 'approved') { + updateData.approved_by = user.id + updateData.approval_date = new Date().toISOString() + } const { error } = await supabase .from('leave_requests') - .update({ status: newStatus }) + .update(updateData) .eq('id', id) if (error) { @@ -63,3 +123,95 @@ export async function updateLeaveStatus(id: string, newStatus: 'approved' | 'rej revalidatePath('/leave-requests') return { success: true } } + +export async function resetLeaveStatus(id: string) { + const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) return { error: 'Oturum bulunamadı.' } + + const { data: leaveReq } = await supabase + .from('leave_requests') + .select('employees(company_id)') + .eq('id', id) + .single() + + if (!leaveReq) return { error: 'Talebe ulaşılamadı.' } + const companyId = (leaveReq.employees as any).company_id + + const { data: currentUserEmployee } = await supabase + .from('employees') + .select('roles(name)') + .eq('user_id', user.id) + .eq('company_id', companyId) + .single() + + const isManager = (currentUserEmployee?.roles as any)?.name === 'admin' || (currentUserEmployee?.roles as any)?.name === 'manager' + if (!isManager) return { error: 'Bu işlem için yetkiniz bulunmamaktadır.' } + + const { error } = await supabase + .from('leave_requests') + .update({ + status: 'pending', + approved_by: null, + approval_date: null + }) + .eq('id', id) + + if (error) return { error: 'Durum sıfırlanırken hata: ' + error.message } + + revalidatePath('/leave-requests') + return { success: true } +} + +export async function updateLeaveRequest(formData: FormData) { + const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) return { error: 'Oturum bulunamadı.' } + + const id = formData.get('id') as string + const startDate = formData.get('start_date') as string + const endDate = formData.get('end_date') as string + const reason = formData.get('reason') as string + const leaveTypeId = formData.get('leave_type_id') as string + + // Fetch current request status and role + const { data: leaveReq } = await supabase + .from('leave_requests') + .select('status, employee_id, employees(company_id, user_id)') + .eq('id', id) + .single() + + if (!leaveReq) return { error: 'Talep bulunamadı.' } + const companyId = (leaveReq.employees as any).company_id + const isOwner = (leaveReq.employees as any).user_id === user.id + + const { data: currentUserEmployee } = await supabase + .from('employees') + .select('roles(name)') + .eq('user_id', user.id) + .eq('company_id', companyId) + .single() + + const isManager = (currentUserEmployee?.roles as any)?.name === 'admin' || (currentUserEmployee?.roles as any)?.name === 'manager' + + // Only owner (if pending) or manager can edit + if (!isManager && (!isOwner || leaveReq.status !== 'pending')) { + return { error: 'Bu talebi düzenleme yetkiniz bulunmamaktadır (Onaylanmış veya yetkisiz işlem).' } + } + + const { error } = await supabase + .from('leave_requests') + .update({ + start_date: startDate, + end_date: endDate, + reason: reason, + leave_type_id: leaveTypeId, + updated_at: new Date().toISOString() + }) + .eq('id', id) + + if (error) return { error: 'Güncellenirken hata: ' + error.message } + + revalidatePath('/leave-requests') + return { success: true } +} diff --git a/src/app/leave-requests/page.tsx b/src/app/leave-requests/page.tsx index 633e71d..4a813c5 100644 --- a/src/app/leave-requests/page.tsx +++ b/src/app/leave-requests/page.tsx @@ -1,247 +1,370 @@ import { createClient } from '@/utils/supabase/server' -import { updateLeaveStatus } from './actions' -import { CalendarDaysIcon, CheckIcon, XMarkIcon } from '@heroicons/react/24/outline' +import { updateLeaveStatus, resetLeaveStatus } from './actions' +import { CalendarDaysIcon, CheckIcon, XMarkIcon, ClockIcon, ArrowPathIcon, PencilIcon } from '@heroicons/react/24/outline' +import LeaveRequestForm from '@/components/LeaveRequestForm' + +export default async function LeaveRequestsPage({ + searchParams, +}: { + searchParams: Promise<{ status?: string; search?: string; type?: string }> +}) { + const params = await searchParams; + const statusFilter = params.status; + const searchFilter = params.search; + const typeFilter = params.type; -export default async function LeaveRequestsPage() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) return null - // Fetch user role - const { data: dbUser } = await supabase - .from('users') - .select('roles(name)') - .eq('id', user.id) - .single() + // Fetch user records with roles for each company + const { data: employeeRecords } = await supabase + .from('employees') + .select('id, company_id, roles(name), companies:company_id(id, name)') + .eq('user_id', user.id) - const roleData: any = dbUser?.roles - const roleName = Array.isArray(roleData) ? roleData[0]?.name : roleData?.name - const isAdminOrManager = roleName === 'admin' || roleName === 'manager' + const adminCompanyIds = employeeRecords + ?.filter((emp: any) => { + const roleName = emp.roles?.name + return roleName === 'admin' || roleName === 'manager' + }) + .map((emp: any) => emp.company_id) || [] - // Fetch leave requests - // If admin/manager, fetch all. If employee, fetch only their own. - let query = supabase + const isAdminSomewhere = adminCompanyIds.length > 0 + + // Fetch all employees ONLY for companies where the user is admin/manager + let allEmployees: any[] = [] + if (isAdminSomewhere) { + const { data: empData } = await supabase + .from('employees') + .select('id, company_id, users(first_name, last_name)') + .in('company_id', adminCompanyIds) + .eq('status', 'active') + allEmployees = empData || [] + } + + // Fetch leave types + const { data: leaveTypes } = await supabase + .from('leave_types') + .select('*') + .order('display_order') + + // Fetch leave balances for the current user + const { data: balances } = await supabase + .from('leave_balances') + .select('*, leave_types!inner(name, display_order)') + .eq('year', new Date().getFullYear()) + .eq('employee_id', employeeRecords?.[0]?.id) // Just a fallback, handled better in query + .order('display_order', { referencedTable: 'leave_types' }) + + // Fetch leave requests (Admins see all for their companies, users see their own) + let requestsQuery = supabase .from('leave_requests') .select(` id, start_date, end_date, + total_days, status, reason, created_at, + leave_type:leave_type_id ( name, color_code ), employees!inner ( id, user_id, + company_id, companies ( name ), users( first_name, last_name, email ) ) `) .order('created_at', { ascending: false }) - if (!isAdminOrManager) { - query = query.eq('employees.user_id', user.id) + if (!isAdminSomewhere) { + requestsQuery = requestsQuery.eq('employees.user_id', user.id) + } else { + // If admin somewhere, they see their own AND all from companies they manage + requestsQuery = requestsQuery.or(`user_id.eq.${user.id},company_id.in.(${adminCompanyIds.join(',')})`, { foreignTable: 'employees' }) } - const { data: requests } = await query + // Apply filters + if (statusFilter) { + requestsQuery = requestsQuery.eq('status', statusFilter) + } + if (typeFilter) { + requestsQuery = requestsQuery.eq('leave_type_id', typeFilter) + } - // Also fetch companies they belong to if they want to request leave - const { data: userCompanies } = await supabase - .from('employees') - .select('companies:company_id(id, name)') - .eq('user_id', user.id) + const { data: requests } = await requestsQuery + + // Manual filtering for search (since it's a join with users) + let filteredRequests = requests || [] + if (searchFilter) { + const term = searchFilter.toLowerCase() + filteredRequests = filteredRequests.filter(req => { + const employee = Array.isArray(req.employees) ? req.employees[0] : req.employees + const user = Array.isArray(employee?.users) ? employee?.users[0] : employee?.users + return ( + `${user?.first_name} ${user?.last_name}`.toLowerCase().includes(term) || + user?.email?.toLowerCase().includes(term) + ) + }) + } return ( -
-
-
-
-
-
-

İ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.'} -

-
+
+
+
+
+
+

İzin Yönetimi

+

+ {isAdminSomewhere + ? 'Personel izin taleplerini yönetin.' + : 'İzin taleplerinizi oluşturun ve takip edin.'} +

+ +
+ +
-
- - {/* Request form - only show if they are actually employed somewhere */} -
-
-

- Yeni İzin Talebi -

+ {/* Modern Filtre Barı */} +
+
+
+ +
- {(userCompanies && userCompanies.length > 0) ? ( - - {/* We use a workaround API route or action for complex auth logic with FormData above */} - {/* For real production, we can inline the action. I'll mock the action call for now */} -
- - -
+ -
- - -
- -
- - -
- -
- -