Files
vberp/src/routes/backoffice/humanresource/employee/+page.svelte
Aji Setiaji fad25ef7a8 fix benefit
2025-09-12 06:01:19 +07:00

1127 lines
44 KiB
Svelte
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.

<script lang="ts">
import { supabase } from "$lib/supabaseClient";
import { formatCurrency } from "$lib/utils/conversion";
import { onMount } from "svelte";
import { writable } from "svelte/store";
import { v4 as uuidv4 } from "uuid";
type EmployeeItem = {
id: string;
employee_name: string;
employee_status: string;
location: string;
department: string;
contract_start: Date;
end_of_contract: Date;
employee_type: string;
date_of_birth: Date;
photo_url: string;
phone: string;
mobile: string;
personal_email: string;
work_email: string;
permanent_address: string;
temporary_address: string;
job_title: string;
emergency_contact_name: string;
emergency_contact_phone: string;
bank_account: string;
jamsostek_id: string;
npwp_id: string;
remarks: string;
salary: number;
last_edu: string;
document: string;
hire_date?: Date;
leaving_date?: Date;
termination_reason?: string;
special_notes?: string;
company?: string;
blood_type?: string;
religion?: string;
marital_status?: string;
id_number?: string;
emergency_contact_relation?: string;
bank_account_name?: string;
bpjs_kesehatan_id?: string;
father_name?: string;
mother_name?: string;
spouse_name?: string;
number_of_children?: number;
child_1?: string;
child_2?: string;
child_3?: string;
place_of_birth?: string;
created_at?: Date;
};
type EmployeeBenefits = {
id: string;
employee_id: string;
basic_salary: number;
position_allowance: number;
meal_allowance: number;
transportation_allowance: number;
created_at: Date;
updated_at: Date;
};
const EmployeeStatus = {
Active: "Active",
Inactive: "Inactive",
};
const EmployeeType = {
DailyWorker: "Daily Worker",
Contract: "Contract",
OutSource: "Outsource",
};
const MaritalStatus = {
Single: "Single",
Married: "Married",
Divorced: "Divorced",
Widowed: "Widowed",
};
let allRows: EmployeeItem[] = [];
let offset = 0;
type columns = {
key: string;
title: string;
};
let search: string = "";
const columns: columns[] = [
{ key: "no", title: "Employee No." },
{ key: "employee_name", title: "Employee Name" },
{ key: "employee_status", title: "Status" },
{ key: "location", title: "Location" },
{ key: "company", title: "Company" },
{ key: "department", title: "Department" },
{ key: "contract_start", title: "Contract Start" },
{ key: "end_of_contract", title: "End of Contract" },
{ key: "hire_date", title: "Hire Date" },
{ key: "leaving_date", title: "Leaving Date" },
{ key: "termination_reason", title: "Termination/Leaving Reason" },
{ key: "special_notes", title: "Special Notes" },
{ key: "employee_type", title: "Type" },
{ key: "place_of_birth", title: "Place of Birth" },
{ key: "date_of_birth", title: "Date of Birth" },
{ key: "id_number", title: "ID Number" },
{ key: "marital_status", title: "Marital Status" },
{ key: "photo_url", title: "Photo" },
{ key: "phone", title: "Phone" },
{ key: "mobile", title: "Mobile" },
{ key: "personal_email", title: "Personal Email" },
{ key: "work_email", title: "Work Email" },
{ key: "permanent_address", title: "Permanent Address" },
{ key: "temporary_address", title: "Temporary Address" },
{ key: "job_title", title: "Job Title" },
{ key: "emergency_contact_name", title: "Emergency Contact Name" },
{
key: "emergency_contact_relation",
title: "Emergency Contact Relation",
},
{ key: "emergency_contact_phone", title: "Emergency Contact Phone" },
{ key: "bank_account", title: "Bank Account" },
{ key: "bank_account_name", title: "Employee Bank Account Name" },
{ key: "jamsostek_id", title: "Jamsostek ID" },
{ key: "bpjs_kesehatan_id", title: "BPJS Kesehatan ID" },
{ key: "npwp_id", title: "NPWP ID" },
{ key: "remarks", title: "Remarks" },
{ key: "salary", title: "Salary" },
{ key: "last_edu", title: "Last Education" },
{ key: "religion", title: "Religion" },
{ key: "blood_type", title: "Blood Type" },
{ key: "father_name", title: "Fathers Name" },
{ key: "mother_name", title: "Mothers Name" },
{ key: "spouse_name", title: "Spouse Name" },
{ key: "number_of_children", title: "Number of Children" },
{ key: "child_1", title: "Child 1" },
{ key: "child_2", title: "Child 2" },
{ key: "child_3", title: "Child 3" },
{ key: "document", title: "Document" },
{ key: "benefits", title: "Benefits" },
{ key: "created_at", title: "Created At" },
{ key: "actions", title: "Actions" },
];
const columnBenefits: columns[] = [
{ key: "basic_salary", title: "Basic Salary" },
{ key: "position_allowance", title: "Position Allowance" },
{ key: "meal_allowance", title: "Meal Allowance" },
{ key: "transportation_allowance", title: "Transportation Allowance" },
{ key: "total_salary", title: "Total Salary" },
{ key: "created_at", title: "Created At" },
{ key: "updated_at", title: "Updated At" },
];
let currentPage = offset + 1;
let rowsPerPage = 10;
let totalItems = 0;
async function fetchEmployee(
searchTerm: string | null = null,
sortColumn: string | null = "created_at",
sortOrder: "asc" | "desc" = "desc",
offset: number = 0,
limit: number = 10,
) {
const fromIndex = offset * limit;
const toIndex = fromIndex + limit - 1;
// Inisialisasi query
let query = supabase
.from("vb_employee")
.select("*", { count: "exact" })
.order(sortColumn || "created_at", {
ascending: sortOrder === "asc",
})
.range(fromIndex, toIndex); // Ini sudah termasuk offset & limit
// Tambahkan filter pencarian jika ada
if (searchTerm) {
query = query.ilike("employee_name", `%${searchTerm}%`);
}
// Jalankan query
const { data, count, error } = await query;
if (error) {
console.error("Error fetching PO Employee:", error);
return;
}
allRows = data as EmployeeItem[];
totalItems = count || 0;
console.log("Fetched Employee:", allRows);
console.log("Total Items:", totalItems);
}
async function fetchEmployeeBenefits(employeeId: string) {
const { data, error } = await supabase
.from("vb_benefits")
.select("*")
.eq("employee_id", employeeId)
.single();
if (error) {
console.error("Error fetching Employee Benefits:", error);
return null;
}
return data as EmployeeBenefits;
}
$: totalPages = Math.ceil(totalItems / rowsPerPage);
function goToPage(page: number) {
if (page < 1 || page > totalPages) return;
currentPage = page;
offset = (currentPage - 1) * rowsPerPage;
fetchEmployee(
search,
"created_at",
"desc",
currentPage - 1,
rowsPerPage,
);
}
function pageRange(
totalPages: number,
currentPage: number,
): (number | string)[] {
const range: (number | string)[] = [];
const maxDisplay = 5;
if (totalPages <= maxDisplay + 2) {
for (let i = 1; i <= totalPages; i++) range.push(i);
} else {
const start = Math.max(2, currentPage - 2);
const end = Math.min(totalPages - 1, currentPage + 2);
range.push(1);
if (start > 2) range.push("...");
for (let i = start; i <= end; i++) {
range.push(i);
}
if (end < totalPages - 1) range.push("...");
range.push(totalPages);
}
return range;
}
function changePage(page: number) {
if (page < 1 || page > totalPages || page === currentPage) return;
currentPage = page;
offset = (currentPage - 1) * rowsPerPage;
fetchEmployee(
search,
"created_at",
"desc",
currentPage - 1,
rowsPerPage,
);
}
onMount(() => {
fetchEmployee();
});
// Initialize the first page
$: currentPage = 1;
let showModal = false;
let showModalBenefits = false;
let isEditing = false;
let currentEditingId: string | null = null;
let newEmployeeInsert: EmployeeItem = {
id: "",
employee_name: "AJITEST",
employee_status: EmployeeStatus.Active,
location: "Nusa Penida",
department: "IT",
contract_start: new Date(),
end_of_contract: new Date(),
hire_date: new Date(),
leaving_date: new Date(),
employee_type: "Contract",
date_of_birth: new Date(),
photo_url:
"https://nusapenida-balitour.com/wp-content/uploads/2022/10/a-glance-nusa-penida-scaled.jpg",
phone: "082177751041",
mobile: "082177751041",
personal_email: "ajitest@example.com",
work_email: "ajitest@workmail.com",
permanent_address: "Jl. Example No. 123, Jakarta",
temporary_address: "Jl. Temporary No. 456, Jakarta",
job_title: "Software Engineer",
emergency_contact_name: "Aji Setiaji",
emergency_contact_phone: "08123456789",
bank_account: "1234567890",
jamsostek_id: "9876543210",
npwp_id: "123456789012345",
remarks: "New employee",
salary: 5000000,
last_edu: "Bachelor's Degree",
document:
"https://nusapenida-balitour.com/wp-content/uploads/2022/10/a-glance-nusa-penida-scaled.jpg",
place_of_birth: "Jakarta",
marital_status: "Single",
id_number: "3171234567890001",
emergency_contact_relation: "Brother",
bank_account_name: "Aji Setiaji",
bpjs_kesehatan_id: "1122334455",
father_name: "Budi Setiaji",
mother_name: "Siti Aminah",
spouse_name: "",
number_of_children: 100,
child_1: "",
child_2: "",
child_3: "",
religion: "Islam",
blood_type: "O",
created_at: new Date(),
};
let newEmployeeBenefits: EmployeeBenefits = {
id: "",
employee_id: "",
basic_salary: 0,
position_allowance: 0,
meal_allowance: 0,
transportation_allowance: 0,
created_at: new Date(),
updated_at: new Date(),
};
let employeeBenefits: Record<string, any> = {};
const excludedKeys = ["id", "actions", "created_at", "no"];
const formColumns = columns.filter(
(col) => !excludedKeys.includes(col.key),
);
const excludedKeysBenefits = [
"id",
"employee_id",
"total_salary",
"created_at",
"benefits",
"updated_at",
];
const formColumnsBenefits = columnBenefits.filter(
(col) => !excludedKeysBenefits.includes(col.key),
);
function openModal(newEmployeeItem?: EmployeeItem, emp?: number) {
if (newEmployeeItem) {
isEditing = true;
currentEditingId = newEmployeeItem.id;
// Copy data to avoid direct mutation
newEmployeeInsert = { ...newEmployeeItem };
// Fetch and populate benefits data
fetchEmployeeBenefits(newEmployeeItem.id).then((benefits) => {
if (benefits) {
newEmployeeBenefits = benefits;
} else {
newEmployeeBenefits = {
id: "",
employee_id: newEmployeeItem.id,
basic_salary: newEmployeeItem.salary || 0,
position_allowance: 0,
meal_allowance: 0,
transportation_allowance: 0,
created_at: new Date(),
updated_at: new Date(),
};
}
});
} else {
isEditing = false;
currentEditingId = null;
newEmployeeInsert = {
id: "",
employee_name: "",
employee_status: "",
location: "",
department: "",
contract_start: new Date(),
end_of_contract: new Date(),
employee_type: "",
date_of_birth: new Date(),
photo_url: "",
phone: "",
mobile: "",
personal_email: "",
work_email: "",
permanent_address: "",
temporary_address: "",
job_title: "",
emergency_contact_name: "",
emergency_contact_phone: "",
bank_account: "",
jamsostek_id: "",
npwp_id: "",
remarks: "",
salary: 0,
last_edu: "",
document: "",
hire_date: new Date(),
leaving_date: new Date(),
termination_reason: "",
special_notes: "",
company: "",
blood_type: "",
religion: "",
marital_status: "",
id_number: "",
emergency_contact_relation: "",
bank_account_name: "",
bpjs_kesehatan_id: "",
father_name: "",
mother_name: "",
spouse_name: "",
number_of_children: 0,
child_1: "",
child_2: "",
child_3: "",
place_of_birth: "",
created_at: new Date(),
};
newEmployeeBenefits = {
id: "",
employee_id: "",
basic_salary: 0,
position_allowance: 0,
meal_allowance: 0,
transportation_allowance: 0,
created_at: new Date(),
updated_at: new Date(),
};
}
showModal = true;
}
async function openModalBenefits(employee: EmployeeItem) {
const benefits = await fetchEmployeeBenefits(employee.id);
if (benefits) {
employeeBenefits = benefits;
employeeBenefits.total_salary = formatCurrency(
employeeBenefits.basic_salary +
employeeBenefits.position_allowance +
employeeBenefits.meal_allowance +
employeeBenefits.transportation_allowance,
);
employeeBenefits.basic_salary = formatCurrency(
employeeBenefits.basic_salary,
);
employeeBenefits.position_allowance = formatCurrency(
employeeBenefits.position_allowance,
);
employeeBenefits.meal_allowance = formatCurrency(
employeeBenefits.meal_allowance,
);
employeeBenefits.transportation_allowance = formatCurrency(
employeeBenefits.transportation_allowance,
);
console.log("Employee Benefits:", employeeBenefits);
} else {
employeeBenefits = {
id: "",
employee_id: employee.id,
basic_salary: employee.salary || 0,
position_allowance: 0,
meal_allowance: 0,
transportation_allowance: 0,
total_salary: 0,
created_at: new Date(),
updated_at: new Date(),
};
}
showModalBenefits = true;
}
async function saveEmployee(event: Event) {
event.preventDefault();
// validate newEmployeeInsert
if (!validateForm(newEmployeeInsert)) {
alert("Please fix the form errors before submitting.");
return;
}
if (isEditing && currentEditingId) {
const { error } = await supabase
.from("vb_employee")
.update(newEmployeeInsert)
.eq("id", currentEditingId);
if (error) {
alert("Error updating Employee: " + error.message);
console.error("Error updating Employee:", error);
return;
}
// cek apakah data vb_benefits sudah ada
const { data: existingBenefits, error: checkError } = await supabase
.from("vb_benefits")
.select("id")
.eq("employee_id", currentEditingId)
.maybeSingle();
if (checkError) {
console.error("Error checking vb_benefits:", checkError);
}
if (existingBenefits) {
// update jika ada
const { error: benefitsError } = await supabase
.from("vb_benefits")
.update(newEmployeeBenefits)
.eq("employee_id", currentEditingId);
if (benefitsError) {
console.error("Error updating vb_benefits:", benefitsError);
}
} else {
const idBenefit = uuidv4();
newEmployeeBenefits.id = idBenefit;
newEmployeeBenefits.employee_id = currentEditingId;
// insert jika belum ada
const { error: benefitsInsertError } = await supabase
.from("vb_benefits")
.insert({
...newEmployeeBenefits,
});
if (benefitsInsertError) {
console.error(
"Error inserting vb_benefits:",
benefitsInsertError,
);
}
}
alert("Employee Updated successfully!");
} else {
newEmployeeInsert.id = uuidv4(); // Generate a new UUID for the ID
const { error } = await supabase
.from("vb_employee")
.insert(newEmployeeInsert);
if (error) {
alert("Error creating New Employee: " + error.message);
console.error("Error creating New Employee:", error);
return;
}
newEmployeeBenefits.id = uuidv4();
newEmployeeBenefits.employee_id = newEmployeeInsert.id;
const { error: benefitsError } = await supabase
.from("vb_employee_benefits")
.insert(newEmployeeBenefits);
if (benefitsError) {
console.error(
"Error creating Employee Benefits:",
benefitsError,
);
return;
} else {
alert("New Employee created successfully!");
}
}
await fetchEmployee(
search,
"created_at",
"desc",
(currentPage - 1) * rowsPerPage,
rowsPerPage,
);
showModal = false;
}
async function deleteEmployee(id: string) {
if (confirm("Are you sure you want to delete this Employee?")) {
const { error } = await supabase
.from("vb_employee")
.delete()
.eq("id", id);
if (error) {
console.error("Error deleting Employee:", error);
return;
}
await fetchEmployee(
search,
"created_at",
"desc",
(currentPage - 1) * rowsPerPage,
rowsPerPage,
);
}
}
export let formErrors = writable<{ [key: string]: string }>({});
function validateForm(newEmployeeInsert: EmployeeItem): boolean {
const errors: { [key: string]: string } = {};
const requiredFields = [
"employee_name",
// Add other required fields here if necessary
];
requiredFields.forEach((field) => {
if (
!newEmployeeInsert[field as keyof EmployeeItem] ||
newEmployeeInsert[field as keyof EmployeeItem] === ""
) {
errors[field] = `${field.replace(/_/g, " ")} is required.`;
}
});
formErrors.set(errors);
return Object.keys(errors).length === 0;
}
function errorClass(field: string): string {
return $formErrors[field] ? "border-red-500" : "border";
}
</script>
<div>
<div
class="p-6 bg-white shadow-md rounded-2xl mb-4 flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4"
>
<div>
<h2
class="text-lg font-semibold text-gray-800 flex items-center gap-2"
>
<span class="text-blue-600">👨‍💼</span>
Employee
</h2>
<p class="text-sm text-gray-600">Manage your employee data here.</p>
</div>
<div class="flex flex-col sm:flex-row sm:items-center gap-2">
<input
type="text"
placeholder="🔍 Search by item name..."
class="border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:outline-none px-4 py-2 rounded-xl text-sm w-64 transition"
on:input={(e) => {
search = (e.target as HTMLInputElement).value.toLowerCase();
if (search !== "" && search.length > 3) {
fetchEmployee(search, "created_at", "desc", 0, 10);
} else if (search === "") {
fetchEmployee(null, "created_at", "desc", 0, 10);
}
}}
/>
<button
class="bg-gray-200 text-gray-700 px-4 py-2 rounded-xl hover:bg-gray-300 text-sm transition"
on:click={() =>
fetchEmployee(search, "created_at", "desc", 0, 10)}
>
🔄 Reset
</button>
<button
class="bg-blue-600 text-white px-4 py-2 rounded-xl hover:bg-blue-700 text-sm transition"
on:click={() => openModal()}
>
New Employee
</button>
</div>
</div>
<div class="overflow-x-auto rounded-lg shadow mb-4">
<table class="w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-100">
<tr>
{#each columns as col}
{#if col.key === "guest_name"}
<th
class="sticky left-0 px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
style="background-color: #f0f8ff; z-index: 10;"
>
{col.title}
</th>
{:else}
<th
class="px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
>
{col.title}
</th>
{/if}
{/each}
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each allRows as row, i}
<tr class="hover:bg-gray-50 transition">
{#each columns as col}
{#if col.key === "no"}
<td class="px-4 py-2 font-medium text-gray-800">
{offset + i + 1}
</td>
{:else if col.key === "employee_name"}
<td class="px-4 py-2 font-medium text-gray-800">
{row[col.key as keyof EmployeeItem]}
</td>
{:else if col.key === "employee_status"}
<td class="px-4 py-2">
{#if row[col.key as keyof EmployeeItem] === EmployeeStatus.Active}
<span
class="inline-block px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800"
>
{EmployeeStatus.Active}
</span>
{:else}
<span
class="inline-block px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800"
>
{EmployeeStatus.Inactive}
</span>
{/if}
</td>
{:else if col.key === "employee_type"}
<td class="px-4 py-2">
{#if row[col.key as keyof EmployeeItem] === EmployeeType.DailyWorker}
<span
class="inline-block px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800"
>
{EmployeeType.DailyWorker}
</span>
{:else if row[col.key as keyof EmployeeItem] === EmployeeType.Contract}
<span
class="inline-block px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800"
>
{EmployeeType.Contract}
</span>
{:else if row[col.key as keyof EmployeeItem] === EmployeeType.OutSource}
<span
class="inline-block px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800"
>
{EmployeeType.OutSource}
</span>
{:else}
<span
class="inline-block px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800"
>
Unknown
</span>
{/if}
</td>
{:else if col.key === "photo_url" || col.key === "document" || col.key === "url"}
<td class="px-4 py-2">
{#if row[col.key as keyof EmployeeItem]}
<a
href={row[
col.key as keyof EmployeeItem
] as string}
target="_blank"
class="text-blue-600 hover:underline"
>View</a
>
{:else}
N/A
{/if}
</td>
{:else if col.key === "salary"}
<td class="px-4 py-2">
{row[col.key as keyof EmployeeItem]
? new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
}).format(
row[
col.key as keyof EmployeeItem
] as number,
)
: "N/A"}
</td>
{:else if col.key === "contract_start" || col.key === "end_of_contract" || col.key === "date_of_birth" || col.key === "hire_date" || col.key === "leaving_date"}
<td class="px-4 py-2">
{row[col.key as keyof EmployeeItem]
? new Date(
row[
col.key as keyof EmployeeItem
] as string | number | Date,
).toLocaleDateString()
: "N/A"}
</td>
{:else if col.key === "benefits"}
<td class="px-4 py-2">
<button
class="inline-flex items-center gap-1 rounded bg-green-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-green-700"
on:click={() => openModalBenefits(row)}
>
💼 View Benefits
</button>
</td>
{:else if col.key === "created_at"}
<td class="px-4 py-2">
{row[col.key as keyof EmployeeItem]
? new Date(
row[
col.key as keyof EmployeeItem
] as string | number | Date,
).toLocaleDateString()
: "N/A"}
</td>
{:else if col.key === "actions"}
<td class="px-4 py-2">
<button
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-blue-700"
on:click={() => openModal(row)}
>
✏️ Edit
</button>
<button
class="inline-flex items-center gap-1 rounded bg-red-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-red-700"
on:click={() => deleteEmployee(row.id)}
>
🗑️ Delete
</button>
</td>
{:else}
<td class="px-4 py-2">
{row[col.key as keyof EmployeeItem]}
</td>
{/if}
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
<!-- Pagination controls -->
<div class="flex justify-between items-center text-sm">
<div>
Showing {(currentPage - 1) * rowsPerPage + 1}
{Math.min(currentPage * rowsPerPage, totalItems)} of {totalItems} items
</div>
<div class="space-x-2">
<button
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
on:click={() => goToPage(currentPage - 1)}
disabled={currentPage === 1}
>
Previous
</button>
{#each pageRange(totalPages, currentPage) as page}
{#if page === "..."}
<span class="px-2">...</span>
{:else}
<button
on:click={() => changePage(page as number)}
class="px-2 py-1 border rounded {page === currentPage
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white border-gray-300 hover:bg-gray-100'}"
>
{page}
</button>
{/if}
{/each}
<button
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
on:click={() => goToPage(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</div>
</div>
{#if showModal}
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div
class="bg-white rounded-lg shadow-lg p-6 w-full max-w-md max-h-[90vh] overflow-y-auto"
>
<h2 class="text-xl font-semibold mb-4">
{isEditing ? "Edit Employee" : "New Employee"}
</h2>
<form on:submit|preventDefault={saveEmployee} class="space-y-4">
{#each formColumns as col}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
{#if col.key === "contract_start" || col.key === "end_of_contract" || col.key === "date_of_birth" || col.key === "hire_date" || col.key === "leaving_date"}
<input
type="date"
id={col.key}
bind:value={newEmployeeInsert[col.key]}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 {errorClass(
col.key,
)}"
/>
{:else if col.key === "salary"}
<input
type="number"
id={col.key}
bind:value={newEmployeeInsert[col.key]}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 {errorClass(
col.key,
)}"
/>
{:else if col.key === "employee_type"}
<select
id={col.key}
bind:value={newEmployeeInsert[col.key]}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 {errorClass(
col.key,
)}"
>
<option value="" disabled>Select type</option>
{#each Object.entries(EmployeeType) as [key, value]}
<option value={key}>{value}</option>
{/each}
</select>
{:else if col.key === "employee_status"}
<select
id={col.key}
bind:value={newEmployeeInsert[col.key]}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 {errorClass(
col.key,
)}"
>
<option value="" disabled>Select status</option>
{#each Object.entries(EmployeeStatus) as [key, value]}
<option value={key}>{value}</option>
{/each}
</select>
{:else if col.key === "marital_status"}
<select
id={col.key}
bind:value={newEmployeeInsert[col.key]}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 {errorClass(
col.key,
)}"
>
<option value="" disabled
>Select marital status</option
>
{#each Object.entries(MaritalStatus) as [key, value]}
<option value={key}>{value}</option>
{/each}
</select>
{:else}
<input
type="text"
id={col.key}
bind:value={
newEmployeeInsert[
col.key as keyof EmployeeItem
]
}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 {errorClass(
col.key,
)}"
/>
{/if}
{#if $formErrors[col.key]}
<p class="text-red-500 text-xs mt-1">
{$formErrors[col.key]}
</p>
{/if}
</div>
{/each}
<h3 class="text-lg font-semibold mb-2">Benefits</h3>
{#each formColumnsBenefits as col}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
{#if col.key === "basic_salary"}
<input
type="number"
id={col.key}
bind:value={newEmployeeBenefits[col.key]}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{:else}
<input
type="number"
id={col.key}
bind:value={
newEmployeeBenefits[
col.key as keyof EmployeeBenefits
]
}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{/if}
</div>
{/each}
<div class="flex justify-end space-x-2">
<button
type="button"
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-xl hover:bg-gray-300"
on:click={() => (showModal = false)}
>
Cancel
</button>
<button
type="submit"
class="px-4 py-2 bg-blue-600 text-white rounded-xl hover:bg-blue-700"
>
{isEditing ? "Update" : "Create"}
</button>
</div>
</form>
</div>
</div>
{/if}
{#if showModalBenefits}
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div
class="bg-white rounded-lg shadow-lg p-6 w-full max-w-md max-h-[90vh] overflow-y-auto"
>
<h2 class="text-xl font-semibold mb-4">Employee Benefits</h2>
<div class="space-y-4">
{#each formColumnsBenefits as col}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
{#if col.key === "basic_salary"}
<input
type="text"
id={col.key}
bind:value={employeeBenefits[col.key]}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
readonly
/>
{:else}
<input
type="text"
id={col.key}
bind:value={
employeeBenefits[
col.key as keyof EmployeeBenefits
]
}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
readonly
/>
{/if}
</div>
{/each}
<div class="mb-4">
<label
for="total_salary"
class="block text-sm font-medium text-gray-700 mb-1"
>
Total Salary
</label>
<input
type="text"
id="total_salary"
bind:value={employeeBenefits.total_salary}
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
readonly
/>
</div>
<div class="flex justify-end">
<button
type="button"
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-xl hover:bg-gray-300"
on:click={() => (showModalBenefits = false)}
>
Close
</button>
</div>
</div>
</div>
</div>
{/if}