diff --git a/src/routes/backoffice/timesheets/+page.svelte b/src/routes/backoffice/timesheets/+page.svelte index aa3b756..2bdfc3d 100644 --- a/src/routes/backoffice/timesheets/+page.svelte +++ b/src/routes/backoffice/timesheets/+page.svelte @@ -7,6 +7,7 @@ type Timesheets = { id: number; entered_by: string; + entered_name?: string; work_description: string; type_of_work: "Running" | "Periodic" | "Irregular"; category_of_work: @@ -24,11 +25,13 @@ remarks: string; approval: boolean; approved_by?: string; + approved_name?: string; approved_date?: Date; created_at?: Date; }; type TimesheetsJoined = Timesheets & { vb_employee: { id: string; employee_name: string } | null; + villa_name?: string; }; type TimesheetDisplay = { id: number; @@ -138,13 +141,8 @@ let currentSearchTerm: string | null = null; let dataVilla: Villa[] = []; let allRows: TimesheetDisplay[] = []; + let totalItems = 0; 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; @@ -200,7 +198,14 @@ console.error("Failed to load villas", villaErr); } - fetchTimeSheets(); + fetchTimeSheets( + currentVillaFilter, + currentSearchTerm, + sortColumn, + sortOrder, + 0, + rowsPerPage, + ); }); // Reactive variables for sorting function toggleSort(column: string) { @@ -210,7 +215,14 @@ sortColumn = column; sortOrder = "asc"; } - fetchTimeSheets(); // re-fetch with new sort + fetchTimeSheets( + currentVillaFilter, + currentSearchTerm, + sortColumn, + sortOrder, + (currentPage - 1) * rowsPerPage, + rowsPerPage, + ); } // Function to calculate total work hours @@ -227,7 +239,22 @@ } // Function to go to a specific page function goToPage(page: number) { + + console.log("Going to page:", page); + if (page >= 1 && page <= totalPages) currentPage = page; + + // Re-fetch timesheets with the current filters and pagination + console.log("Fetching timesheets for page:", currentPage); + + fetchTimeSheets( + currentVillaFilter, + currentSearchTerm, + sortColumn, + sortOrder, + (currentPage - 1) * rowsPerPage, + rowsPerPage, + ); } // Function to open the modal for adding or editing a timesheet function openModal(tsdata?: Record) { @@ -272,129 +299,260 @@ showModal = true; } - // Function to fetch timesheets with optional filters and sorting + + // Function to fetch timesheets async function fetchTimeSheets( villaIdFilter: string | null = null, searchTerm: string | null = null, + sortColumn: string | null = "created_at", + sortOrder: "asc" | "desc" = "desc", offset: number = 0, - limit: number = 1000, - ) { - let reportedBy: { label: string; value: string }[] = []; - const { data: staffData, error: staffError } = await supabase - .from("vb_employee") - .select("id, employee_name") - .eq("employee_status", "Active") - .order("employee_name", { ascending: true }); + limit: number = 20, + ) { - if (staffError) { - console.error("Error fetching staff:", staffError); - } else if (staffData) { - reportedBy = staffData.map((s) => ({ - label: s.employee_name, - value: s.id, - })); - } + console.log("Fetching timesheets with filters:", { + villaIdFilter, + searchTerm, + sortColumn, + sortOrder, + offset, + limit, + }); + + + const fromIndex = offset; + const toIndex = offset + limit - 1; let query = supabase - .from("vb_timesheet") - .select(`*`) + .from("vb_timesheet_data") + .select( + '*', + { count: "exact" }, + ) .order(sortColumn || "created_at", { - ascending: sortOrder === "asc", - }); + ascending: sortOrder === "asc", + }).range(fromIndex, toIndex); + + if (searchTerm) { + query = query.ilike("work_description", `%${searchTerm}%`) + } if (villaIdFilter) { - const { data: villaMatch } = await supabase - .from("vb_villas") - .select("id") - .eq("villa_name", villaIdFilter); - - const matchedId = villaMatch?.[0]?.id; - if (matchedId) { - query = query.eq("villa_id", matchedId); - } + query = query.eq("villa_name", villaIdFilter); } - if (offset) { - query = query.range(offset, offset + limit - 1); - } - if (limit) { - query = query.limit(limit); - } - const { data: timesheet, error } = await query; + // Jalankan query + const { data, count, error } = await query; + + console.log("Fetched timesheets:", count); + + console.log("Fetched timesheets data:", data); + + + if (error) { console.error("Error fetching timesheets:", error); return; } - const loweredSearch = searchTerm?.toLowerCase(); - let filteredTimesheet = timesheet; - if (loweredSearch) { - filteredTimesheet = timesheet.filter((ts) => { - const workDesc = ts.work_description?.toLowerCase() || ""; - const staffName = reportedBy.find((s) => s.value === ts.entered_by)?.label?.toLowerCase() || ""; - return ( - workDesc.includes(loweredSearch) || - staffName.includes(loweredSearch) - ); - }); - } - - const villaIds = [...new Set(filteredTimesheet.map((i: Timesheets) => i.villa_id))]; - let villas = []; - if (villaIds.length > 0) { - const { data: villasData, error: villaError } = await supabase - .from("vb_villas") - .select("*") - .in("id", villaIds); - - if (villaError) { - console.error("Error fetching villas:", villaError); - } else { - villas = villasData; - } - } - const { data: approvers, error: approverError } = await supabase - .from("vb_users") - .select("id, full_name"); - - if (approverError) { - console.error("Error fetching approvers:", approverError); - } - allRows = filteredTimesheet.map((tsdata: TimesheetsJoined) => { - const villa = villas.find((v) => v.id === tsdata.villa_id); - const approver = approvers?.find((u) => u.id === tsdata.approved_by); - + allRows = data.map((tsdata: TimesheetsJoined) => { return { - id: tsdata.id, - name: tsdata.work_description, - staff_id: - reportedBy.find((s) => s.value === tsdata.entered_by)?.label || "Unknown", - date_in: new Date(tsdata.datetime_in), - date_out: new Date(tsdata.datetime_out), - type_of_work: tsdata.type_of_work, - category_of_work: tsdata.category_of_work, - villa_name: villa ? villa.villa_name : "Unknown Villa", - approval: - tsdata.approval == null - ? "PENDING" - : tsdata.approval - ? "APPROVED" - : "REJECTED", - total_hours_work: - Math.abs( - new Date(tsdata.datetime_out).getTime() - new Date(tsdata.datetime_in).getTime() - ) / (1000 * 60 * 60), - approved_by: approver?.full_name ?? "Not Approved", - approved_date: tsdata.approved_date, - remarks: tsdata.remarks, - // created_at: tsdata.created_at ? new Date(tsdata.created_at) : undefined, + id: tsdata.id, + name: tsdata.work_description, + staff_id: tsdata.entered_name || "Unknown Staff", + date_in: new Date(tsdata.datetime_in), + date_out: new Date(tsdata.datetime_out), + type_of_work: tsdata.type_of_work, + category_of_work: tsdata.category_of_work, + villa_name: tsdata.villa_name || "Unknown Villa", + approval: + tsdata.approval == null + ? "PENDING" + : tsdata.approval + ? "APPROVED" + : "REJECTED", + total_hours_work: + Math.abs( + new Date(tsdata.datetime_out).getTime() - + new Date(tsdata.datetime_in).getTime(), + ) / (1000 * 60 * 60), + approved_by: tsdata.approved_name || "Not Approved", + approved_date: tsdata.approved_date + ? new Date(tsdata.approved_date) + : undefined, + remarks: tsdata.remarks || "No remarks", + created_at: tsdata.created_at + ? new Date(tsdata.created_at) + : undefined, } as TimesheetDisplay; }); - currentPage = 1; - console.log("Fetched rows:", allRows); + totalItems = count || 0; } + $: totalPages = Math.ceil(totalItems / rowsPerPage); + $: currentPage = 1; + + 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; + fetchTimeSheets( + currentVillaFilter, + currentSearchTerm, + sortColumn, + sortOrder, + (currentPage - 1) * rowsPerPage, + rowsPerPage, + ); + } + + // // Function to fetch timesheets with optional filters and sorting + // async function fetchTimeSheets( + // villaIdFilter: string | null = null, + // searchTerm: string | null = null, + // offset: number = 0, + // limit: number = 1000, + // ) { + // let reportedBy: { label: string; value: string }[] = []; + // const { data: staffData, error: staffError } = await supabase + // .from("vb_employee") + // .select("id, employee_name") + // .eq("employee_status", "Active") + // .order("employee_name", { ascending: true }); + + // if (staffError) { + // console.error("Error fetching staff:", staffError); + // } else if (staffData) { + // reportedBy = staffData.map((s) => ({ + // label: s.employee_name, + // value: s.id, + // })); + // } + + // let query = supabase + // .from("vb_timesheet") + // .select(`*`) + // .order(sortColumn || "created_at", { + // ascending: sortOrder === "asc", + // }); + + // if (villaIdFilter) { + // const { data: villaMatch } = await supabase + // .from("vb_villas") + // .select("id") + // .eq("villa_name", villaIdFilter); + + // const matchedId = villaMatch?.[0]?.id; + // if (matchedId) { + // query = query.eq("villa_id", matchedId); + // } + // } + + // if (offset) { + // query = query.range(offset, offset + limit - 1); + // } + // if (limit) { + // query = query.limit(limit); + // } + // const { data: timesheet, error } = await query; + // if (error) { + // console.error("Error fetching timesheets:", error); + // return; + // } + + // const loweredSearch = searchTerm?.toLowerCase(); + // let filteredTimesheet = timesheet; + // if (loweredSearch) { + // filteredTimesheet = timesheet.filter((ts) => { + // const workDesc = ts.work_description?.toLowerCase() || ""; + // const staffName = reportedBy.find((s) => s.value === ts.entered_by)?.label?.toLowerCase() || ""; + // return ( + // workDesc.includes(loweredSearch) || + // staffName.includes(loweredSearch) + // ); + // }); + // } + + // const villaIds = [...new Set(filteredTimesheet.map((i: Timesheets) => i.villa_id))]; + // let villas = []; + // if (villaIds.length > 0) { + // const { data: villasData, error: villaError } = await supabase + // .from("vb_villas") + // .select("*") + // .in("id", villaIds); + + // if (villaError) { + // console.error("Error fetching villas:", villaError); + // } else { + // villas = villasData; + // } + // } + // const { data: approvers, error: approverError } = await supabase + // .from("vb_users") + // .select("id, full_name"); + + // if (approverError) { + // console.error("Error fetching approvers:", approverError); + // } + // allRows = filteredTimesheet.map((tsdata: TimesheetsJoined) => { + // const villa = villas.find((v) => v.id === tsdata.villa_id); + // const approver = approvers?.find((u) => u.id === tsdata.approved_by); + + // return { + // id: tsdata.id, + // name: tsdata.work_description, + // staff_id: + // reportedBy.find((s) => s.value === tsdata.entered_by)?.label || "Unknown", + // date_in: new Date(tsdata.datetime_in), + // date_out: new Date(tsdata.datetime_out), + // type_of_work: tsdata.type_of_work, + // category_of_work: tsdata.category_of_work, + // villa_name: villa ? villa.villa_name : "Unknown Villa", + // approval: + // tsdata.approval == null + // ? "PENDING" + // : tsdata.approval + // ? "APPROVED" + // : "REJECTED", + // total_hours_work: + // Math.abs( + // new Date(tsdata.datetime_out).getTime() - new Date(tsdata.datetime_in).getTime() + // ) / (1000 * 60 * 60), + // approved_by: approver?.full_name ?? "Not Approved", + // approved_date: tsdata.approved_date, + // remarks: tsdata.remarks, + // // created_at: tsdata.created_at ? new Date(tsdata.created_at) : undefined, + // } as TimesheetDisplay; + // }); + // currentPage = 1; + + // console.log("Fetched rows:", allRows); + // } + // Function to delete a timesheet @@ -446,6 +604,8 @@ let error = null; if (isEditing && currentEditingId) { + + const { error: updateError } = await supabase .from("vb_timesheet") .update({ @@ -486,6 +646,7 @@ total_work_hour: 0, remarks: "", approval: null, + created_at: new Date().toISOString(), }; await fetchTimeSheets(); showModal = false; @@ -584,7 +745,7 @@ - {#each paginatedRows as row} + {#each allRows as row} {#each columns as col} {#if col.key === "name"} @@ -633,20 +794,8 @@ {:else if col.key === "approved_date"} {row[col.key] && - !isNaN(Date.parse(String(row[col.key]))) - ? new Date( - row[ - col.key as keyof TimesheetDisplay - ] as string | number | Date, - ).toLocaleDateString("en-GB", { - timeZone: "Asia/Singapore", - day: "2-digit", - month: "2-digit", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - hour12: false, - }) + !isNaN(new Date(row[col.key] as string | number | Date).getTime()) + ? new Date(row[col.key] as string | number | Date).toLocaleString() : "N/A"} {:else if col.key === "total_hours_work"} @@ -656,20 +805,7 @@ {:else if col.key === "created_at"} {row[col.key] !== undefined - ? new Date( - row[col.key]!, - ).toLocaleDateString( - "en-GB", - { - timeZone: "Asia/Singapore", - day: "2-digit", - month: "2-digit", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - hour12: false, - }, - ) + ? new Date(row[col.key] as string | number | Date).toLocaleString() : "N/A"} {:else if col.key === "villa_name"} @@ -704,18 +840,7 @@ {:else if col.key === "date_in" || col.key === "date_out"} {row[col.key] - ? new Date(row[col.key]).toLocaleString( - "en-GB", - { - timeZone: "Asia/Singapore", - day: "2-digit", - month: "2-digit", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - hour12: false, - }, - ) + ? new Date(row[col.key]).toLocaleString() : "N/A"} {:else} @@ -733,10 +858,39 @@
- Showing {(currentPage - 1) * rowsPerPage + 1}–{Math.min(currentPage * rowsPerPage, allRows.length)} - of {allRows.length} + Showing {(currentPage - 1) * rowsPerPage + 1}– + {Math.min(currentPage * rowsPerPage, totalItems)} of {totalItems} items +
+
+ + {#each pageRange(totalPages, currentPage) as page} + {#if page === "..."} + ... + {:else} + + {/if} + {/each} +
-