clean code
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
|
||||||
type Timesheets = {
|
type Timesheets = {
|
||||||
id: number;
|
id: number;
|
||||||
entered_by: string;
|
entered_by: string;
|
||||||
@@ -76,6 +77,10 @@
|
|||||||
key: string;
|
key: string;
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
type Employee = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const categoryOfWork = [
|
const categoryOfWork = [
|
||||||
@@ -93,6 +98,7 @@
|
|||||||
{ label: "Irregular", value: "Irregular" },
|
{ label: "Irregular", value: "Irregular" },
|
||||||
];
|
];
|
||||||
const columns: columns[] = [
|
const columns: columns[] = [
|
||||||
|
{ key: "villa_name", title: "Villa Name" },
|
||||||
{ key: "name", title: "Work Description" },
|
{ key: "name", title: "Work Description" },
|
||||||
{ key: "staff_id", title: "Staff Name" },
|
{ key: "staff_id", title: "Staff Name" },
|
||||||
{ key: "date_in", title: "Date In" },
|
{ key: "date_in", title: "Date In" },
|
||||||
@@ -100,7 +106,6 @@
|
|||||||
{ key: "type_of_work", title: "Type of Work" },
|
{ key: "type_of_work", title: "Type of Work" },
|
||||||
{ key: "category_of_work", title: "Category of Work" },
|
{ key: "category_of_work", title: "Category of Work" },
|
||||||
{ key: "approval", title: "Approval" },
|
{ key: "approval", title: "Approval" },
|
||||||
{ key: "villa_name", title: "Villa Name" },
|
|
||||||
{ key: "approved_by", title: "Approved By" },
|
{ key: "approved_by", title: "Approved By" },
|
||||||
{ key: "approved_date", title: "Approved/Rejected Date" },
|
{ key: "approved_date", title: "Approved/Rejected Date" },
|
||||||
{ key: "total_hours_work", title: "Total Hours Work" },
|
{ key: "total_hours_work", title: "Total Hours Work" },
|
||||||
@@ -108,34 +113,92 @@
|
|||||||
{ key: "created_at", title: "Created At" },
|
{ key: "created_at", title: "Created At" },
|
||||||
{ key: "actions", title: "Actions" },
|
{ 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 currentUserId: string | null = null;
|
||||||
let currentVillaFilter: string | null = null;
|
let currentVillaFilter: string | null = null;
|
||||||
let currentSearchTerm: string | null = null;
|
let currentSearchTerm: string | null = null;
|
||||||
let dataVilla: Villa[] = [];
|
let dataVilla: Villa[] = [];
|
||||||
let allRows: TimesheetDisplay[] = [];
|
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 () => {
|
onMount(async () => {
|
||||||
|
// get current user
|
||||||
const {
|
const {
|
||||||
data: { user },
|
data: { user },
|
||||||
} = await supabase.auth.getUser();
|
} = await supabase.auth.getUser();
|
||||||
|
|
||||||
currentUserId = user?.id ?? null;
|
currentUserId = user?.id ?? null;
|
||||||
});
|
// fetch employees
|
||||||
onMount(async () => {
|
const { data: empData, error: empErr } = await supabase
|
||||||
const { data, error } = 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")
|
.from("vb_villas")
|
||||||
.select("id, villa_name")
|
.select("id, villa_name")
|
||||||
.eq("villa_status", "Active");
|
.eq("villa_status", "Active")
|
||||||
|
.order("villa_name", { ascending: true });
|
||||||
|
|
||||||
if (error) {
|
if (!villaErr && villaData) {
|
||||||
console.error("Error fetching villas:", error);
|
villas = villaData;
|
||||||
} else if (data) {
|
} else {
|
||||||
dataVilla = data;
|
console.error("Failed to load villas", villaErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchTimeSheets();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to calculate total work hours
|
// Function to calculate total work hours
|
||||||
function calculateTotalHours() {
|
function calculateTotalHours() {
|
||||||
if (form.datetime_in && form.datetime_out) {
|
if (form.datetime_in && form.datetime_out) {
|
||||||
@@ -148,7 +211,75 @@
|
|||||||
form.total_work_hour = 0;
|
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
|
// Function to fetch timesheets with optional filters and sorting
|
||||||
async function fetchTimeSheets(
|
async function fetchTimeSheets(
|
||||||
villaNameFilter: string | null = null,
|
villaNameFilter: string | null = null,
|
||||||
@@ -264,146 +395,7 @@
|
|||||||
});
|
});
|
||||||
// Sort the rows based on the sortColumn and sortOrder
|
// Sort the rows based on the sortColumn and sortOrder
|
||||||
}
|
}
|
||||||
let currentPage = 1;
|
// Function to delete a timesheet
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
async function deleteTimesheet(id: string) {
|
async function deleteTimesheet(id: string) {
|
||||||
if (confirm("Are you sure you want to delete this Timesheet?")) {
|
if (confirm("Are you sure you want to delete this Timesheet?")) {
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
@@ -417,31 +409,7 @@
|
|||||||
await fetchTimeSheets();
|
await fetchTimeSheets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Function to update the approval status of a timesheet
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateApprovalStatus(
|
async function updateApprovalStatus(
|
||||||
id: string,
|
id: string,
|
||||||
status: string,
|
status: string,
|
||||||
@@ -464,6 +432,7 @@
|
|||||||
await fetchTimeSheets();
|
await fetchTimeSheets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Function to submit the form data
|
||||||
async function submitForm() {
|
async function submitForm() {
|
||||||
calculateTotalHours();
|
calculateTotalHours();
|
||||||
|
|
||||||
@@ -520,6 +489,9 @@
|
|||||||
showModal = false;
|
showModal = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export let formErrors = writable<{ [key: string]: string }>({});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -606,19 +578,18 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</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}
|
{#each paginatedRows as row}
|
||||||
<tr class="hover:bg-gray-50 transition">
|
<tr class="hover:bg-gray-50 transition">
|
||||||
{#each columns as col}
|
{#each columns as col}
|
||||||
{#if col.key === "name"}
|
{#if col.key === "name"}
|
||||||
<td
|
<td
|
||||||
class="sticky left-0 px-4 py-2 font-medium text-blue-600"
|
class="left-0 px-4 py-2 max-w-xs whitespace-normal align-top break-words"
|
||||||
style="background-color: #f0f8ff; cursor: pointer;"
|
|
||||||
>
|
>
|
||||||
{row[col.key]}
|
{row[col.key]}
|
||||||
</td>
|
</td>
|
||||||
{:else if col.key === "approval"}
|
{:else if col.key === "approval"}
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2 align-top">
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-1 rounded px-2 py-1 text-xs font-medium
|
class="inline-flex items-center gap-1 rounded px-2 py-1 text-xs font-medium
|
||||||
{row[col.key] === 'APPROVED'
|
{row[col.key] === 'APPROVED'
|
||||||
@@ -678,7 +649,8 @@
|
|||||||
: "N/A"}
|
: "N/A"}
|
||||||
</td>
|
</td>
|
||||||
{:else if col.key === "villa_name"}
|
{: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"}
|
{row[col.key] || "Unknown Villa"}
|
||||||
</td>
|
</td>
|
||||||
{:else if col.key === "staff_id"}
|
{:else if col.key === "staff_id"}
|
||||||
|
|||||||
Reference in New Issue
Block a user