perbaikan HR
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { supabase } from "$lib/supabaseClient";
|
||||
import { onMount } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
type EmployeeItem = {
|
||||
id: string;
|
||||
@@ -17,12 +18,12 @@
|
||||
phone: string;
|
||||
mobile: string;
|
||||
personal_email: string;
|
||||
wok_email: string;
|
||||
work_email: string;
|
||||
permanent_address: string;
|
||||
temporary_address: string;
|
||||
job_title: string;
|
||||
emergency_contact_name: string;
|
||||
emergency_contract_phone: string;
|
||||
emergency_contact_phone: string;
|
||||
bank_account: string;
|
||||
jamsostek_id: string;
|
||||
npwp_id: string;
|
||||
@@ -34,10 +35,15 @@
|
||||
created_at?: Date;
|
||||
};
|
||||
|
||||
type POItem = {
|
||||
id: number;
|
||||
item_name: string;
|
||||
created_at?: Date;
|
||||
const EmployeeStatus = {
|
||||
Active: "Active",
|
||||
Inactive: "Inactive",
|
||||
};
|
||||
|
||||
const EmployeeType = {
|
||||
DailyWorker: "Daily Worker",
|
||||
Contract: "Contract",
|
||||
OutSource: "Outsource",
|
||||
};
|
||||
|
||||
let allRows: EmployeeItem[] = [];
|
||||
@@ -63,12 +69,12 @@
|
||||
{ key: "phone", title: "Phone" },
|
||||
{ key: "mobile", title: "Mobile" },
|
||||
{ key: "personal_email", title: "Personal Email" },
|
||||
{ key: "wok_email", title: "Work 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_contract_phone", title: "Emergency Contact Phone" },
|
||||
{ key: "emergency_contact_phone", title: "Emergency Contact Phone" },
|
||||
{ key: "bank_account", title: "Bank Account" },
|
||||
{ key: "jamsostek_id", title: "Jamsostek ID" },
|
||||
{ key: "npwp_id", title: "NPWP ID" },
|
||||
@@ -132,13 +138,7 @@
|
||||
currentPage = page;
|
||||
offset = (currentPage - 1) * rowsPerPage;
|
||||
|
||||
fetchEmployee(
|
||||
null,
|
||||
"created_at",
|
||||
"desc",
|
||||
(currentPage - 1) * rowsPerPage,
|
||||
rowsPerPage,
|
||||
);
|
||||
fetchEmployee(null, "created_at", "desc", currentPage - 1, rowsPerPage);
|
||||
}
|
||||
|
||||
function pageRange(
|
||||
@@ -173,13 +173,7 @@
|
||||
currentPage = page;
|
||||
offset = (currentPage - 1) * rowsPerPage;
|
||||
|
||||
fetchEmployee(
|
||||
null,
|
||||
"created_at",
|
||||
"desc",
|
||||
(currentPage - 1) * rowsPerPage,
|
||||
rowsPerPage,
|
||||
);
|
||||
fetchEmployee(null, "created_at", "desc", currentPage - 1, rowsPerPage);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -194,32 +188,34 @@
|
||||
let currentEditingId: string | null = null;
|
||||
let newEmployeeInsert: EmployeeItem = {
|
||||
id: "",
|
||||
employee_name: "",
|
||||
employee_status: "",
|
||||
location: "",
|
||||
department: "",
|
||||
employee_name: "AJITEST",
|
||||
employee_status: EmployeeStatus.Active,
|
||||
location: "Nusa Penida",
|
||||
department: "IT",
|
||||
contract_start: new Date(),
|
||||
end_of_contract: new Date(),
|
||||
employee_type: "",
|
||||
employee_type: "Contract",
|
||||
date_of_birth: new Date(),
|
||||
photo_url: "",
|
||||
phone: "",
|
||||
mobile: "",
|
||||
personal_email: "",
|
||||
wok_email: "",
|
||||
permanent_address: "",
|
||||
temporary_address: "",
|
||||
job_title: "",
|
||||
emergency_contact_name: "",
|
||||
emergency_contract_phone: "",
|
||||
bank_account: "",
|
||||
jamsostek_id: "",
|
||||
npwp_id: "",
|
||||
remarks: "",
|
||||
salary: 0,
|
||||
last_edu: "",
|
||||
document: "",
|
||||
url: "",
|
||||
photo_url:
|
||||
"https://nusapenida-balitour.com/wp-content/uploads/2022/10/a-glance-nusa-penida-scaled.jpg",
|
||||
phone: "08123456789",
|
||||
mobile: "08123456789",
|
||||
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",
|
||||
url: "https://example.com/employee/ajitest",
|
||||
created_at: new Date(),
|
||||
};
|
||||
|
||||
@@ -255,12 +251,12 @@
|
||||
phone: "",
|
||||
mobile: "",
|
||||
personal_email: "",
|
||||
wok_email: "",
|
||||
work_email: "",
|
||||
permanent_address: "",
|
||||
temporary_address: "",
|
||||
job_title: "",
|
||||
emergency_contact_name: "",
|
||||
emergency_contract_phone: "",
|
||||
emergency_contact_phone: "",
|
||||
bank_account: "",
|
||||
jamsostek_id: "",
|
||||
npwp_id: "",
|
||||
@@ -278,16 +274,12 @@
|
||||
async function saveEmployee(event: Event) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.target as HTMLFormElement);
|
||||
|
||||
// Validate form data
|
||||
if (!validateForm(formData)) {
|
||||
console.error("Form validation failed");
|
||||
// validate newEmployeeInsert
|
||||
if (!validateForm(newEmployeeInsert)) {
|
||||
alert("Please fix the form errors before submitting.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Saving Employee:", newEmployeeInsert);
|
||||
|
||||
if (isEditing && currentEditingId) {
|
||||
const { error } = await supabase
|
||||
.from("vb_employee")
|
||||
@@ -302,6 +294,8 @@
|
||||
alert("Employee updated successfully!");
|
||||
}
|
||||
} else {
|
||||
newEmployeeInsert.id = uuidv4(); // Generate a new UUID for the ID
|
||||
|
||||
const { error } = await supabase
|
||||
.from("vb_employee")
|
||||
.insert(newEmployeeInsert);
|
||||
@@ -347,7 +341,7 @@
|
||||
|
||||
export let formErrors = writable<{ [key: string]: string }>({});
|
||||
|
||||
function validateForm(formData: FormData): boolean {
|
||||
function validateForm(newEmployeeInsert: EmployeeItem): boolean {
|
||||
const errors: { [key: string]: string } = {};
|
||||
const requiredFields = [
|
||||
"employee_name",
|
||||
@@ -355,7 +349,10 @@
|
||||
];
|
||||
|
||||
requiredFields.forEach((field) => {
|
||||
if (!formData.get(field) || formData.get(field) === "") {
|
||||
if (
|
||||
!newEmployeeInsert[field as keyof EmployeeItem] ||
|
||||
newEmployeeInsert[field as keyof EmployeeItem] === ""
|
||||
) {
|
||||
errors[field] = `${field.replace(/_/g, " ")} is required.`;
|
||||
}
|
||||
});
|
||||
@@ -377,7 +374,7 @@
|
||||
<h2
|
||||
class="text-lg font-semibold text-gray-800 flex items-center gap-2"
|
||||
>
|
||||
<span class="text-blue-600">👥</span>
|
||||
<span class="text-blue-600">👨💼</span>
|
||||
Employee
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600">Manage your employee data here.</p>
|
||||
@@ -443,6 +440,86 @@
|
||||
<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"}
|
||||
<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 === "created_at"}
|
||||
<td class="px-4 py-2">
|
||||
{row[col.key as keyof EmployeeItem]
|
||||
@@ -529,23 +606,22 @@
|
||||
<h2 class="text-xl font-semibold mb-4">
|
||||
{isEditing ? "Edit Employee" : "New Employee"}
|
||||
</h2>
|
||||
<form on:submit={saveEmployee} class="space-y-4">
|
||||
|
||||
<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
|
||||
>
|
||||
{col.title}
|
||||
</label>
|
||||
|
||||
{#if col.key === "contract_start" || col.key === "end_of_contract" || col.key === "date_of_birth"}
|
||||
<input
|
||||
type="date"
|
||||
id={col.key}
|
||||
bind:value={
|
||||
newEmployeeInsert[
|
||||
col.key as keyof EmployeeItem
|
||||
]
|
||||
}
|
||||
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,
|
||||
)}"
|
||||
@@ -554,15 +630,37 @@
|
||||
<input
|
||||
type="number"
|
||||
id={col.key}
|
||||
bind:value={
|
||||
newEmployeeInsert[
|
||||
col.key as keyof EmployeeItem
|
||||
]
|
||||
}
|
||||
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}
|
||||
<input
|
||||
type="text"
|
||||
@@ -577,6 +675,7 @@
|
||||
)}"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if $formErrors[col.key]}
|
||||
<p class="text-red-500 text-xs mt-1">
|
||||
{$formErrors[col.key]}
|
||||
@@ -584,6 +683,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user