clean code

This commit is contained in:
2025-07-01 16:43:23 +08:00
parent a825334e3a
commit b4f5706522

View File

@@ -3,6 +3,7 @@
import { onMount } from "svelte";
import { writable } from "svelte/store";
type Timesheets = {
id: number;
entered_by: string;
@@ -76,6 +77,10 @@
key: string;
title: string;
};
type Employee = {
id: string;
name: string;
};
const categoryOfWork = [
@@ -93,6 +98,7 @@
{ label: "Irregular", value: "Irregular" },
];
const columns: columns[] = [
{ key: "villa_name", title: "Villa Name" },
{ key: "name", title: "Work Description" },
{ key: "staff_id", title: "Staff Name" },
{ key: "date_in", title: "Date In" },
@@ -100,7 +106,6 @@
{ key: "type_of_work", title: "Type of Work" },
{ key: "category_of_work", title: "Category of Work" },
{ key: "approval", title: "Approval" },
{ key: "villa_name", title: "Villa Name" },
{ key: "approved_by", title: "Approved By" },
{ key: "approved_date", title: "Approved/Rejected Date" },
{ key: "total_hours_work", title: "Total Hours Work" },
@@ -108,32 +113,90 @@
{ key: "created_at", title: "Created At" },
{ key: "actions", title: "Actions" },
];
const excludedKeys = ["id"];
const formColumns = columns.filter(
(col) => !excludedKeys.includes(col.key),
);
const typeOfWorkOptions = ["Running", "Periodic", "Irregular"];
const categoryOptions = [
"Cleaning",
"Gardening/Pool",
"Maintenance",
"Supervision",
"Guest Service",
"Administration",
"Non Billable",
];
// Store for current user ID and filters
// reactive variables
let currentUserId: string | null = null;
let currentVillaFilter: string | null = null;
let currentSearchTerm: string | null = null;
let dataVilla: Villa[] = [];
let allRows: TimesheetDisplay[] = [];
let currentPage = 1;
let rowsPerPage = 20;
$: totalPages = Math.ceil(allRows.length / rowsPerPage);
$: paginatedRows = allRows.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage,
);
$: currentPage = 1;
let showModal = false;
let isEditing = false;
let currentEditingId: string | null = null;
let newTsdata: Record<string, any> = {};
let employees: Employee[] = [];
let villas: Villa[] = [];
let form = {
entered_by: "",
work_description: "",
type_of_work: "Running",
category_of_work: "Cleaning",
villa_id: "",
datetime_in: "",
datetime_out: "",
total_work_hour: 0,
remarks: "",
approval: null, // Default null
};
// Fetch initial data on mount
onMount(async () => {
// get current user
const {
data: { user },
} = await supabase.auth.getUser();
currentUserId = user?.id ?? null;
});
onMount(async () => {
const { data, error } = await supabase
// fetch employees
const { data: empData, error: empErr } = await supabase
.from("vb_employee")
.select("id, employee_name")
.eq("employee_status", "Active")
.order("employee_name", { ascending: true });
if (!empErr && empData) {
employees = empData.map((e) => ({
id: e.id,
name: e.employee_name,
}));
} else {
console.error("Failed to load employees", empErr);
}
// fetch villas
const { data: villaData, error: villaErr } = await supabase
.from("vb_villas")
.select("id, villa_name")
.eq("villa_status", "Active");
.eq("villa_status", "Active")
.order("villa_name", { ascending: true });
if (error) {
console.error("Error fetching villas:", error);
} else if (data) {
dataVilla = data;
if (!villaErr && villaData) {
villas = villaData;
} else {
console.error("Failed to load villas", villaErr);
}
fetchTimeSheets();
});
// Function to calculate total work hours
@@ -148,7 +211,75 @@
form.total_work_hour = 0;
}
}
// Function to go to a specific page
function goToPage(page: number) {
if (page >= 1 && page <= totalPages) currentPage = page;
}
// Function to open the modal for adding or editing a timesheet
function openModal(tsdata?: Record<string, any>) {
if (tsdata) {
// Edit mode
isEditing = true;
currentEditingId = tsdata.id;
form = {
entered_by:
employees.find((e) => e.name === tsdata.staff_id)?.id || "",
work_description: tsdata.name,
type_of_work: tsdata.type_of_work,
category_of_work: tsdata.category_of_work,
villa_id:
villas.find((v) => v.villa_name === tsdata.villa_name)?.id ||
"",
datetime_in: tsdata.date_in?.toISOString().slice(0, 16),
datetime_out: tsdata.date_out?.toISOString().slice(0, 16),
total_work_hour: 0,
remarks: tsdata.remarks,
approval: null, // leave null or bring in if editing allowed
};
calculateTotalHours();
} else {
// Add mode
isEditing = false;
currentEditingId = null;
form = {
entered_by: "",
work_description: "",
type_of_work: "Running",
category_of_work: "Cleaning",
villa_id: "",
datetime_in: "",
datetime_out: "",
total_work_hour: 0,
remarks: "",
approval: null,
};
}
showModal = true;
}
// Function to validate the form data
function validateForm(formData: FormData): boolean {
const errors: { [key: string]: string } = {};
const requiredFields = [
"work_description",
"type_of_work",
"category_of_work",
"villa_id",
"date_in",
"date_out",
"remarks",
];
requiredFields.forEach((field) => {
if (!formData.get(field) || formData.get(field) === "") {
errors[field] = `${field.replace(/_/g, " ")} is required.`;
}
});
formErrors.set(errors);
return Object.keys(errors).length === 0;
}
// Function to fetch timesheets with optional filters and sorting
async function fetchTimeSheets(
villaNameFilter: string | null = null,
@@ -264,146 +395,7 @@
});
// Sort the rows based on the sortColumn and sortOrder
}
let currentPage = 1;
let rowsPerPage = 20;
$: totalPages = Math.ceil(allRows.length / rowsPerPage);
$: paginatedRows = allRows.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage,
);
function goToPage(page: number) {
if (page >= 1 && page <= totalPages) currentPage = page;
}
onMount(async () => {
// get current user
const {
data: { user },
} = await supabase.auth.getUser();
currentUserId = user?.id ?? null;
// fetch employees
const { data: empData, error: empErr } = await supabase
.from("vb_employee")
.select("id, employee_name")
.eq("employee_status", "Active")
.order("employee_name", { ascending: true });
if (!empErr && empData) {
employees = empData.map((e) => ({
id: e.id,
name: e.employee_name,
}));
} else {
console.error("Failed to load employees", empErr);
}
// fetch villas
const { data: villaData, error: villaErr } = await supabase
.from("vb_villas")
.select("id, villa_name")
.eq("villa_status", "Active")
.order("villa_name", { ascending: true });
if (!villaErr && villaData) {
villas = villaData;
} else {
console.error("Failed to load villas", villaErr);
}
fetchTimeSheets(); // this still calls your table loader
});
onMount(() => {
fetchTimeSheets();
});
// Initialize the first page
$: currentPage = 1;
let showModal = false;
let isEditing = false;
let currentEditingId: string | null = null;
let newTsdata: Record<string, any> = {};
const excludedKeys = ["id"];
const formColumns = columns.filter(
(col) => !excludedKeys.includes(col.key),
);
function openModal(tsdata?: Record<string, any>) {
if (tsdata) {
// Edit mode
isEditing = true;
currentEditingId = tsdata.id;
form = {
entered_by:
employees.find((e) => e.name === tsdata.staff_id)?.id || "",
work_description: tsdata.name,
type_of_work: tsdata.type_of_work,
category_of_work: tsdata.category_of_work,
villa_id:
villas.find((v) => v.villa_name === tsdata.villa_name)?.id ||
"",
datetime_in: tsdata.date_in?.toISOString().slice(0, 16),
datetime_out: tsdata.date_out?.toISOString().slice(0, 16),
total_work_hour: 0,
remarks: tsdata.remarks,
approval: null, // leave null or bring in if editing allowed
};
calculateTotalHours();
} else {
// Add mode
isEditing = false;
currentEditingId = null;
form = {
entered_by: "",
work_description: "",
type_of_work: "Running",
category_of_work: "Cleaning",
villa_id: "",
datetime_in: "",
datetime_out: "",
total_work_hour: 0,
remarks: "",
approval: null,
};
}
showModal = true;
}
type Employee = {
id: string;
name: string;
};
let employees: Employee[] = [];
let villas: Villa[] = [];
const typeOfWorkOptions = ["Running", "Periodic", "Irregular"];
const categoryOptions = [
"Cleaning",
"Gardening/Pool",
"Maintenance",
"Supervision",
"Guest Service",
"Administration",
"Non Billable",
];
let form = {
entered_by: "",
work_description: "",
type_of_work: "Running",
category_of_work: "Cleaning",
villa_id: "",
datetime_in: "",
datetime_out: "",
total_work_hour: 0,
remarks: "",
approval: null, // Default null
};
// Function to delete a timesheet
async function deleteTimesheet(id: string) {
if (confirm("Are you sure you want to delete this Timesheet?")) {
const { error } = await supabase
@@ -417,31 +409,7 @@
await fetchTimeSheets();
}
}
export let formErrors = writable<{ [key: string]: string }>({});
function validateForm(formData: FormData): boolean {
const errors: { [key: string]: string } = {};
const requiredFields = [
"work_description",
"type_of_work",
"category_of_work",
"villa_id",
"date_in",
"date_out",
"remarks",
];
requiredFields.forEach((field) => {
if (!formData.get(field) || formData.get(field) === "") {
errors[field] = `${field.replace(/_/g, " ")} is required.`;
}
});
formErrors.set(errors);
return Object.keys(errors).length === 0;
}
// Function to update the approval status of a timesheet
async function updateApprovalStatus(
id: string,
status: string,
@@ -464,6 +432,7 @@
await fetchTimeSheets();
}
}
// Function to submit the form data
async function submitForm() {
calculateTotalHours();
@@ -520,6 +489,9 @@
showModal = false;
}
}
export let formErrors = writable<{ [key: string]: string }>({});
</script>
<div>
@@ -606,19 +578,18 @@
{/each}
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
<tbody class="divide-y divide-gray-200 bg-white text-align-top">
{#each paginatedRows as row}
<tr class="hover:bg-gray-50 transition">
{#each columns as col}
{#if col.key === "name"}
<td
class="sticky left-0 px-4 py-2 font-medium text-blue-600"
style="background-color: #f0f8ff; cursor: pointer;"
class="left-0 px-4 py-2 max-w-xs whitespace-normal align-top break-words"
>
{row[col.key]}
</td>
{:else if col.key === "approval"}
<td class="px-4 py-2">
<td class="px-4 py-2 align-top">
<span
class="inline-flex items-center gap-1 rounded px-2 py-1 text-xs font-medium
{row[col.key] === 'APPROVED'
@@ -678,7 +649,8 @@
: "N/A"}
</td>
{:else if col.key === "villa_name"}
<td class="px-4 py-2">
<td class="sticky left-0 px-4 py-2 font-medium text-blue-600 max-w-xs whitespace-normal align-top break-words"
style="background-color: #f0f8ff; cursor: pointer;">
{row[col.key] || "Unknown Villa"}
</td>
{:else if col.key === "staff_id"}