From 600c0d64a8bea2b7d462e67b907d38c9f400c57f Mon Sep 17 00:00:00 2001 From: Aji Setiaji Date: Mon, 14 Jul 2025 12:12:33 +0700 Subject: [PATCH 1/3] perbaikan refresh timesheet --- src/routes/backoffice/timesheets/+page.svelte | 156 +++--------------- 1 file changed, 24 insertions(+), 132 deletions(-) diff --git a/src/routes/backoffice/timesheets/+page.svelte b/src/routes/backoffice/timesheets/+page.svelte index ddcb7d8..6234627 100644 --- a/src/routes/backoffice/timesheets/+page.svelte +++ b/src/routes/backoffice/timesheets/+page.svelte @@ -163,7 +163,6 @@ total_work_hour: 0, remarks: "", approval: null, // Default null - created_at: new Date().toISOString(), }; // Fetch initial data on mount onMount(async () => { @@ -477,132 +476,6 @@ } } - - // // 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 async function deleteTimesheet(id: string) { if (confirm("Are you sure you want to delete this Timesheet?")) { @@ -614,7 +487,14 @@ console.error("Error deleting Timesheet:", error); return; } - await fetchTimeSheets(); + await fetchTimeSheets( + currentVillaFilter, + currentSearchTerm, + sortColumn, + sortOrder, + (currentPage - 1) * rowsPerPage, + rowsPerPage, + ); } } // Function to update the approval status of a timesheet @@ -637,7 +517,14 @@ if (error) { console.error("Error updating approval status:", error); } else { - await fetchTimeSheets(); + await fetchTimeSheets( + currentVillaFilter, + currentSearchTerm, + sortColumn, + sortOrder, + (currentPage - 1) * rowsPerPage, + rowsPerPage, + ); } } // Function to submit the form data @@ -684,7 +571,6 @@ total_work_hour: form.total_work_hour, remarks: form.remarks, approval: form.approval || null, // Allow null for new entries - created_at: new Date().toISOString(), }; const { error: insertError } = await supabase @@ -709,9 +595,15 @@ total_work_hour: 0, remarks: "", approval: null, - created_at: new Date().toISOString(), }; - await fetchTimeSheets(); + await fetchTimeSheets( + currentVillaFilter, + currentSearchTerm, + sortColumn, + sortOrder, + (currentPage - 1) * rowsPerPage, + rowsPerPage, + ); showModal = false; } } From 5b8c44f4948d62264eac3c15ed8f5b4a70cf85f9 Mon Sep 17 00:00:00 2001 From: arteons Date: Wed, 16 Jul 2025 07:59:12 +0800 Subject: [PATCH 2/3] add Sort Function --- .gitignore | 1 + src/lib/CurrencyInput.svelte | 6 +- src/routes/backoffice/issue/+page.svelte | 33 ++++- .../backoffice/purchaseorder/+page.svelte | 134 +++++++++++++++--- src/routes/backoffice/timesheets/+page.svelte | 24 +++- yarn.lock | 44 ++---- 6 files changed, 182 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index 73a2524..30bf1ec 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* package-lock.json +yarn.lock \ No newline at end of file diff --git a/src/lib/CurrencyInput.svelte b/src/lib/CurrencyInput.svelte index 69437cf..a7626a1 100644 --- a/src/lib/CurrencyInput.svelte +++ b/src/lib/CurrencyInput.svelte @@ -2,7 +2,8 @@ export let value: number = 0; // The raw number export let label: string = ""; // Field label export let onInput: (() => void) | null = null; // Optional extra handler - + export let disabled: boolean = false; + export let className: string = ""; let formatted = ""; // Format whenever value changes @@ -26,7 +27,8 @@ type="text" bind:value={formatted} placeholder="Rp 0" - class="w-full border p-2 rounded" + class="w-full border p-2 rounded ${className}" on:input={handleInput} + disabled={disabled} /> \ No newline at end of file diff --git a/src/routes/backoffice/issue/+page.svelte b/src/routes/backoffice/issue/+page.svelte index a6c4f75..a584407 100644 --- a/src/routes/backoffice/issue/+page.svelte +++ b/src/routes/backoffice/issue/+page.svelte @@ -270,6 +270,8 @@ let dataUser: User[] = []; let projectIssueMap: Set = new Set(); let purchaseOrderMap: Set = new Set(); + let sortColumn: string | null = "created_at"; // or any default column + let sortOrder: "asc" | "desc" = "desc"; let poItems: POItem[] = []; let newPO: PurchaseOrder = { villa_id: "", @@ -349,11 +351,20 @@ const { data } = await supabase.from("vb_po_item").select("item_name"); if (data) poItems = data; } + function getDBColumn(key: string) { + switch (key) { + case "villa_name": return "villa_name"; + case "reported_by": return "reported_by"; + // add mappings if needed + default: return key; + } + } // Fetch issues with optional filters async function fetchIssues() { let query = supabase .from("vb_issues_data") - .select("*").order("issue_number", { ascending: true }); + .select("*") + .order(sortColumn || "created_at", { ascending: sortOrder === "asc" }); const { data: issues, error } = await query; @@ -599,7 +610,15 @@ alert("Purchase Order submitted!"); showPurchaseOrderModal = false; } - + function toggleSort(column: string) { + if (sortColumn === column) { + sortOrder = sortOrder === "asc" ? "desc" : "asc"; + } else { + sortColumn = column; + sortOrder = "asc"; + } + fetchIssues(); // re-fetch or re-sort your rows + } // Function to open purchase order modal function openPurchaseOrderModal(issue) { if (purchaseOrderMap.has(issue.id)) { @@ -787,7 +806,7 @@ -
+
@@ -796,14 +815,22 @@ {:else} {/if} {/each} diff --git a/src/routes/backoffice/purchaseorder/+page.svelte b/src/routes/backoffice/purchaseorder/+page.svelte index 7c22ce1..0ce9828 100644 --- a/src/routes/backoffice/purchaseorder/+page.svelte +++ b/src/routes/backoffice/purchaseorder/+page.svelte @@ -161,6 +161,8 @@ updated_by: "", updated_at: new Date().toISOString(), }; + let sortColumn: string | null = "created_at"; // or any default + let sortOrder: "asc" | "desc" = "desc"; let showEditModal = false; let editForm = { po_number: "", @@ -191,7 +193,13 @@ approval: "", approved_by: "", approved_date: "", - reject_comment: "" + reject_comment: "", + po_remark: "", + approved_quantity: 0, + approved_vendor: "", + approved_price: 0, + po_item: "", + total_approved_order_amount: 0 }; let villaOptions = []; let poItemOptions = []; @@ -283,6 +291,21 @@ currency: "IDR", minimumFractionDigits: 0, }); + // function Sort + function toggleSort(column: string) { + if (sortColumn === column) { + sortOrder = sortOrder === "asc" ? "desc" : "asc"; + } else { + sortColumn = column; + sortOrder = "asc"; + } + fetchPurchaseOrder( + selectedVillaId, + searchTerm, + sortColumn, + sortOrder, + ); + } // Function to format numbers as ordinal (1st, 2nd, 3rd, etc.) function ordinal(num: number) { @@ -323,7 +346,13 @@ approval: row.approval ? "approve" : "reject", approved_by: currentUserId, approved_date: new Date().toISOString().split("T")[0], - reject_comment: row.reject_comment || "" + reject_comment: row.reject_comment || "", + po_remark: row.po_remark || "", + approved_quantity: row.approved_quantity || 0, + approved_vendor: row.approved_vendor || "", + approved_price: row.approved_price || 0, + po_item: row.po_item || "", + total_approved_order_amount: row.total_approved_order_amount || 0 }; showApprovalModal = true; @@ -478,7 +507,8 @@ approved_by: approvalForm.approval === "approve" ? currentUserId : null, approved_date: approvalForm.approved_date, reject_comment: approvalForm.reject_comment || null, - po_status: "approved" + po_status: "approved", + po_remark: approvalForm.po_remark || null }) .eq("id", selectedPO.id); @@ -692,7 +722,7 @@ let query = supabase .from("vb_purchaseorder_data") .select("*") - .order(sort || "purchase_order_number", { ascending: order === "desc" }) + .order(sortColumn || "created_at", { ascending: sortOrder === "asc" }) .range(offset, offset + limit - 1); if (filter) { @@ -1070,7 +1100,7 @@ -
+
toggleSort(col.key)} > {col.title} + {#if sortColumn === col.key} + {sortOrder === 'asc' ? ' 🔼' : ' 🔽'} + {/if} toggleSort(col.key)} > {col.title} + {#if sortColumn === col.key} + {sortOrder === 'asc' ? ' 🔼' : ' 🔽'} + {/if}
@@ -1079,14 +1109,22 @@ {:else} {/if} {/each} @@ -1650,11 +1688,10 @@ {#each [1,2,3,4,5,6] as num} - @@ -1667,7 +1704,7 @@ - +
@@ -1693,27 +1730,89 @@ class="w-full border p-2 bg-gray-100" /> - + + {#if approvalForm.po_item} + + + {/if} + + + {#if approvalForm.approved_quantity != null} + + + {/if} + + + {#if approvalForm.approved_price != null} + + + {/if} + + + {#if approvalForm.total_approved_order_amount != null} + + + {/if} + + + {#if approvalForm.approved_vendor} + + + {/if} + + + + {#if approvalForm.approval === 'reject'} - - + > {/if} - - + + + - + + @@ -1726,6 +1825,7 @@
{/if} + {#if showAcknowledgedModal}
diff --git a/src/routes/backoffice/timesheets/+page.svelte b/src/routes/backoffice/timesheets/+page.svelte index 6234627..25f941c 100644 --- a/src/routes/backoffice/timesheets/+page.svelte +++ b/src/routes/backoffice/timesheets/+page.svelte @@ -332,9 +332,10 @@ '*', { count: "exact" }, ) - .order(sortColumn || "created_at", { - ascending: sortOrder === "asc", - }).range(fromIndex, toIndex); + .order(getDBColumn(sortColumn) || "created_at", { + ascending: sortOrder === "asc" + }) + .range(fromIndex, toIndex); if (typeof searchTerm === "string" && searchTerm.length > 4) { // Supabase ilike only supports one column at a time, so use or for multiple columns @@ -422,7 +423,13 @@ return range; } - + function getDBColumn(key: string) { + switch (key) { + case "name": return "work_description"; + case "staff_id": return "entered_by"; + default: return key; + } + } function changePage(page: number) { if (page < 1 || page > totalPages || page === currentPage) return; currentPage = page; @@ -673,7 +680,7 @@
-
+
toggleSort(col.key)} > {col.title} + {#if sortColumn === col.key} + {sortOrder === 'asc' ? ' 🔼' : ' 🔽'} + {/if} toggleSort(col.key)} > {col.title} + {#if sortColumn === col.key} + {sortOrder === 'asc' ? ' 🔼' : ' 🔽'} + {/if}
@@ -682,8 +689,13 @@ {:else}
toggleSort(col.key)} + > {col.title} + {#if sortColumn === col.key} + {sortOrder === 'asc' ? ' 🔼' : ' 🔽'} + {/if} {/if} {/each} diff --git a/yarn.lock b/yarn.lock index 3b805f1..a65c4bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,10 +10,10 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@esbuild/linux-x64@0.25.4": +"@esbuild/win32-x64@0.25.4": version "0.25.4" - resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz" - integrity sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA== + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz" + integrity sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ== "@floating-ui/core@^1.5.0", "@floating-ui/core@^1.7.0": version "1.7.0" @@ -119,20 +119,10 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-linux-x64-gnu@4.41.1": +"@rollup/rollup-win32-x64-msvc@4.41.1": version "4.41.1" - resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz" - integrity sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A== - -"@rollup/rollup-linux-x64-gnu@4.9.5": - version "4.9.5" - resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz" - integrity sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA== - -"@rollup/rollup-linux-x64-musl@4.41.1": - version "4.41.1" - resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz" - integrity sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ== + resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz" + integrity sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw== "@supabase/auth-js@2.69.1": version "2.69.1" @@ -268,15 +258,10 @@ source-map-js "^1.2.1" tailwindcss "4.1.7" -"@tailwindcss/oxide-linux-x64-gnu@4.1.7": +"@tailwindcss/oxide-win32-x64-msvc@4.1.7": version "4.1.7" - resolved "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.7.tgz" - integrity sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg== - -"@tailwindcss/oxide-linux-x64-musl@4.1.7": - version "4.1.7" - resolved "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.7.tgz" - integrity sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA== + resolved "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.7.tgz" + integrity sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ== "@tailwindcss/oxide@4.1.7": version "4.1.7" @@ -525,15 +510,10 @@ kleur@^4.1.5: resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -lightningcss-linux-x64-gnu@1.30.1: +lightningcss-win32-x64-msvc@1.30.1: version "1.30.1" - resolved "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz" - integrity sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== - -lightningcss-linux-x64-musl@1.30.1: - version "1.30.1" - resolved "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz" - integrity sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== + resolved "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz" + integrity sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== lightningcss@^1.21.0, lightningcss@1.30.1: version "1.30.1" From 41df6bfa568d78e323aa17c18de8c5e4e695aa0f Mon Sep 17 00:00:00 2001 From: arteons Date: Thu, 17 Jul 2025 13:41:18 +0800 Subject: [PATCH 3/3] add purchase page --- src/routes/backoffice/timesheets/+page.svelte | 8 +- src/routes/purchaseorder/+page.svelte | 274 ++++++++++++++++++ src/routes/timesheet/+page.svelte | 15 +- 3 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 src/routes/purchaseorder/+page.svelte diff --git a/src/routes/backoffice/timesheets/+page.svelte b/src/routes/backoffice/timesheets/+page.svelte index 25f941c..82cff4b 100644 --- a/src/routes/backoffice/timesheets/+page.svelte +++ b/src/routes/backoffice/timesheets/+page.svelte @@ -1,8 +1,6 @@ + +
+
+ Villa Logo + +

Timesheet Entry

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
{form.total_work_hour}
+
+ +
+ + +
+ + +
+
diff --git a/src/routes/timesheet/+page.svelte b/src/routes/timesheet/+page.svelte index f5aa5bd..44b8898 100644 --- a/src/routes/timesheet/+page.svelte +++ b/src/routes/timesheet/+page.svelte @@ -20,6 +20,7 @@ datetime_out: string; total_work_hour: number; remarks: string; + approved_date: string | null; approval: boolean | null; // Allow null for new entries }; @@ -46,9 +47,9 @@ datetime_out: "", total_work_hour: 0, remarks: "", + approved_date: null, approval: null, // Default null }; - const typeOfWorkOptions: TimesheetForm["type_of_work"][] = [ "Running", "Periodic", @@ -115,7 +116,16 @@ alert("Please select a villa."); return; } - + if (form.approval) { + const today = new Date(); + const yyyy = today.getFullYear(); + const mm = String(today.getMonth() + 1).padStart(2, "0"); + const dd = String(today.getDate()).padStart(2, "0"); + form.approved_date = `${yyyy}/${mm}/${dd}`; + } else { + form.approved_date = null; + } + form.approval = form.total_work_hour <= 1; const { error } = await supabase.from("vb_timesheet").insert([form]); if (error) { @@ -263,7 +273,6 @@ placeholder="Optional remarks" > -