diff --git a/src/components/Sidebar.svelte b/src/components/Sidebar.svelte index c7e01f3..c97897b 100644 --- a/src/components/Sidebar.svelte +++ b/src/components/Sidebar.svelte @@ -1,5 +1,21 @@ -
-
+

Backoffice

Manage your application

@@ -38,16 +100,38 @@ (active = item.name)} + ${activeUrl === item.url ? "bg-blue-100 text-blue-600 font-semibold" : "text-gray-700 hover:bg-blue-50"} + ${item.sub ? "justify-between" : ""} + `} + on:click|preventDefault={() => handleMenuClick(item)} > - {item.icon} - {item.name} + + {item.icon} + {item.name} + + {#if item.sub} + {openMenus[item.name] ? "▲" : "▼"} + {/if} + {#if item.sub && openMenus[item.name]} + + {/if} {/each} diff --git a/src/routes/backoffice/account/+page.svelte b/src/routes/backoffice/account/+page.svelte new file mode 100644 index 0000000..f3a2e29 --- /dev/null +++ b/src/routes/backoffice/account/+page.svelte @@ -0,0 +1,662 @@ + + +
+
+
+

👤 User List

+

+ Manage and view all users in the system. +

+
+ +
+ { + const searchTerm = (e.target as HTMLInputElement).value; + fetchData( + "", + "created_at", + "desc", + searchTerm, + 0, + rowsPerPage, + ); + }} + /> + + +
+
+
+ + + + {#each columns as col} + + {/each} + + + + {#if allRows.length === 0} + + + + + + {:else} + + {#each allRows as row, i} + + {#each columns as col} + + {/each} + + + {/each} + + {/if} +
+ {col.title} + + Actions +
+
+ + + + No users found + Try adjusting your search or add a new + user. +
+
+ {#if col.key === "no"} + {offset + i + 1} + {:else if col.key === "is_active" || col.key === "is_verified" || col.key === "is_deleted"} + {(row as Record)[col.key] + ? "✅" + : "❌"} + {:else if col.key === "profile_picture"} + {#if row.profile_picture} + {#if typeof row[col.key] === "string" && row[col.key]} + {#await getPublicUrl(row[col.key] as string) then publicUrl} + View Picture + {:catch} + Error loading image + {/await} + {:else} + No Picture + {/if} + {/if} + {:else} + {(row as Record)[col.key]} + {/if} + + + +
+
+ + +
+
+ Showing {offset + 1}–{Math.min(offset + rowsPerPage, totalItems)} of + {totalItems} +
+
+ + {#each Array(totalPages) + .fill(0) + .map((_, i) => i + 1) as page} + + {/each} + +
+
+
+ + +{#if showModal} +
+
+

+ {isEditing ? "Edit User" : "Add New User"} +

+ {#each formColumns as col} +
+ + {#if col.key === "role"} + + {#if $formErrors.role} +

+ {$formErrors.role} +

+ {/if} + {:else if col.key === "email"} + + {#if $formErrors.email} +

+ {$formErrors.email} +

+ {/if} + {:else if col.key === "password"} + + {#if $formErrors.password} +

+ {$formErrors.password} +

+ {/if} + {:else if col.key === "phone"} + + {#if $formErrors.phone} +

+ {$formErrors.phone} +

+ {/if} + {:else if col.key === "profile_picture"} +
+ + {#if imagePreviewUrl} + Preview +

+ Preview of selected image +

+ {:else if newUser.profile_picture} + {#await getPublicUrl(newUser.profile_picture) then publicUrl} + Profile Picture + {:catch} + Error loading image + {/await} + {:else} + No Image + {/if} +
+ {:else if col.key === "is_active" || col.key === "is_verified" || col.key === "is_deleted"} + + {:else} + + {#if $formErrors[col.key]} +

+ {$formErrors[col.key]} +

+ {/if} + {/if} +
+ {/each} +
+ + +
+
+
+{/if} diff --git a/src/routes/backoffice/issue/+page.svelte b/src/routes/backoffice/issue/+page.svelte index 7123fac..607eda1 100644 --- a/src/routes/backoffice/issue/+page.svelte +++ b/src/routes/backoffice/issue/+page.svelte @@ -130,6 +130,7 @@ type Issue = { id: string; name: string; + villa_id: string; villa_name: string; area_of_villa: string; priority: string; @@ -148,11 +149,12 @@ follow_up: boolean; need_approval: boolean; created_at: string; + // Optional field to hold the name of the villa }; type issueInsert = { name: string; - villa_name: string; + villa_id: string; area_of_villa: string; priority: string; issue_type: string; @@ -244,7 +246,7 @@ } // Ambil semua villa_id unik dari issues - const villaIds = [...new Set(issues.map((i: Issue) => i.villa_name))]; + const villaIds = [...new Set(issues.map((i: Issue) => i.villa_id))]; const { data: villas, error: villaError } = await supabase .from("villas") @@ -260,7 +262,7 @@ allRows = issues.map((issue: Issue) => ({ ...issue, villa_name: - villas.find((v) => v.id === issue.villa_name).name || null, + villas.find((v) => v.id === issue.villa_id).name || null, })); } @@ -268,10 +270,6 @@ let rowsPerPage = limit; $: totalPages = Math.ceil(totalItems / rowsPerPage); - function editIssue(id: number) { - alert(`Edit issue with ID ${id}`); - } - function goToPage(page: number) { if (page >= 1 && page <= totalPages) currentPage = page; } @@ -352,7 +350,7 @@ } else { const issueInsert: issueInsert = { name: formData.get("name") as string, - villa_name: formData.get("villa_name") as string, + villa_id: formData.get("villa_id") as string, area_of_villa: formData.get("area_of_villa") as string, priority: formData.get("priority") as string, issue_type: formData.get("issue_type") as string, @@ -429,7 +427,7 @@ "name", "description_of_the_issue", "issue_source", - "villa_name", + "villa_id", "reported_date", "reported_by", "priority", @@ -1029,15 +1027,15 @@

{/if}
- {:else if col.key === "villa_name"} + {:else if col.key === "villa_id"}
- {#if $formErrors.villa_name} + {#if $formErrors.villa_id}

- {$formErrors.villa_name} + {$formErrors.villa_id}

{/if}
diff --git a/src/routes/backoffice/project/+page.svelte b/src/routes/backoffice/project/+page.svelte index 8d1207d..e79a57f 100644 --- a/src/routes/backoffice/project/+page.svelte +++ b/src/routes/backoffice/project/+page.svelte @@ -10,6 +10,7 @@ input_by: string; project_due_date: string; picture_link: string; + villa_data?: string; // Optional, if not always present }; type insetProject = { @@ -31,7 +32,6 @@ input_by: string; issue_number: string; issue_id: string; - villa_name: string; report_date: string; project_due_date: string; }; @@ -82,12 +82,34 @@ if (page >= 1 && page <= totalPages) currentPage = page; } - async function fetchProjects() { - // Fetch all projects - const { data, error } = await supabase - .from("projects") - .select("*") - .order("id", { ascending: false }); + async function fetchProjects( + filter: string | null = null, + searchTerm: string | null = null, + sortBy: string | null = null, + sortOrder: "asc" | "desc" = "asc", + offset: number = 0, + limit: number = 10, + ) { + let query = supabase + .from("projects_data") + .select("*", { count: "exact" }) + .order(sortBy || "created_at", { ascending: sortOrder === "asc" }) + .range(offset, offset + limit - 1); + // Apply filter if provided + if (filter) { + query = query.eq("priority", filter); + } + // Apply search term if provided + if (searchTerm) { + query = query.ilike("issue_name", `%${searchTerm}%`); + } + + // Fetch projects + const { data, error } = await query; + if (error) { + console.error("Error fetching projects:", error); + return; + } // ambil issue_id dari projects kemudian ambil data issue yang sesuai const issueIds = data?.map((project: Project) => project.issue_id); @@ -103,6 +125,8 @@ return; } + allRows = []; // Reset allRows before populating + // Set allRows to the combined data allRows = data.map((project: Project) => { const issue = issueData.find( @@ -122,7 +146,7 @@ area_of_villa: issue ? issue.area_of_villa : "Unknown", input_by: project.input_by, issue_number: issue ? issue.issue_number : "Unknown", - villa_name: issue ? issue.villa_name : "Unknown", + villa_name: issue ? project.villa_data : "Unknown", report_date: issue ? issue.reported_date : "Unknown", project_due_date: project.project_due_date, }; @@ -357,20 +381,53 @@
-
+
-

Project List

+

📋 Project List

- Manage your projects here. You can add, edit, or delete - projects. + Manage your projects and tasks efficiently.

- +
+ { + const searchTerm = ( + e.target as HTMLInputElement + ).value.toLowerCase(); + fetchProjects(null, searchTerm, "created_at", "desc"); + }} + /> + + + +
diff --git a/src/routes/backoffice/purchaseorder/+page.svelte b/src/routes/backoffice/purchaseorder/+page.svelte index 2a9e654..645d04f 100644 --- a/src/routes/backoffice/purchaseorder/+page.svelte +++ b/src/routes/backoffice/purchaseorder/+page.svelte @@ -122,12 +122,28 @@ if (page >= 1 && page <= totalPages) currentPage = page; } - async function fetchPurchaseOrder() { - const { data, error } = await supabase - .from("purchase_orders") - .select("*") - .order("created_at", { ascending: false }); - + async function fetchPurchaseOrder( + filter: string | null = null, + search: string | null = null, + sort: string | null = null, + order: "asc" | "desc" = "desc", + offset: number = 0, + limit: number = 1000, + ) { + let query = supabase + .from("purchaseorder_data") + .select( + "id, purchase_order_number, villa_data, issue_id, prepared_date, po_type, po_quantity, po_status, approved_vendor, acknowledged, acknowledge_by, approved_price, approved_quantity, total_approved_order_amount, approval, completed_status, received, received_by, input_by, approved_by, created_at", + ) + .order(sort || "created_at", { ascending: order === "asc" }) + .range(offset, offset + limit - 1); + if (filter) { + query = query.eq("po_type", filter); + } + if (search) { + query = query.ilike("purchase_order_number", `%${search}%`); + } + const { data, error } = await query; if (error) { console.error("Error fetching purchase orders:", error); return; @@ -158,7 +174,7 @@ // masukkan villa name dan issue name ke dalam data allRows = data.map((row) => { const issue = issues.find((issue) => issue.id === row.issue_id); - const villa = villas.find((villa) => villa.id === issue.villa_name); + const villa = villas.find((villa) => villa.id === issue.villa_id); const vendor = vendors.find( (vendor) => vendor.id === row.approved_vendor, ); @@ -166,7 +182,7 @@ return { ...row, name: issue ? issue.name : "Unknown Issue", - villa_name: villa ? villa.name : "Unknown Villa", + villa_name: row.villa_data, priority: issue ? issue.priority : "Unknown Priority", approved_vendor: vendor ? vendor.name @@ -476,9 +492,14 @@
-
+
-

+

+ 📦 Purchase Order List

@@ -486,12 +507,36 @@ delete purchase orders as needed.

- +
+ { + const searchTerm = ( + e.target as HTMLInputElement + ).value.toLowerCase(); + fetchPurchaseOrder(null, searchTerm, "created_at", "desc"); + }} + /> + + +
@@ -526,33 +571,6 @@ > {row[col.key as keyof PurchaseOrderDisplay]} - {:else if col.key === "po_status"} - {:else if col.key === "approval"} {:else if col.key === "received"} @@ -621,6 +641,7 @@ ); } }} + disabled /> {:else if col.key === "completed_status"} @@ -645,9 +666,10 @@ ); } }} + disabled > ON PROSES + import { onMount } from "svelte"; + import Select from "svelte-select"; + import { supabase } from "$lib/supabaseClient"; + + type PurchaseOrderInsert = { + issue_id: string; + prepared_date: string; + po_type: string; + po_quantity: number; + po_status: string; + approved_vendor: string; + acknowledge_by: string; + approved_by: string; + approved_price: number; + completed_status: string; + }; + + let purchaseOrderInsert: PurchaseOrderInsert = { + issue_id: "", + prepared_date: "", + po_type: "", + po_quantity: 0, + po_status: "REQUESTED", + approved_vendor: "", + acknowledge_by: "", + approved_by: "", + approved_price: 0, + completed_status: "", + }; + + type PurchaseOrders = { + id: string; + purchase_order_number: string; + prepared_date: string; + po_type: string; + po_quantity: number; + po_status: string; + approved_vendor: string; + acknowledged: boolean; + approved_vendor_id: string; + acknowledge_by: string; + approved_price: number; + approved_quantity: number; + total_approved_order_amount: number; + approval: string; + completed_status: string; + received: boolean; + received_by: string; + input_by: string; + issue_id: string; + approved_by: string; + created_at: string; + }; + + type PurchaseOrderDisplay = { + id: string; + name: string; + purchase_order_number: string; + villa_name: string; + priority: string; + prepared_date: string; + po_type: string; + po_quantity: number; + po_status: string; + approved_vendor: string; + acknowledged: boolean; + acknowledge_by: string; + approved_by: string; + approved_price: number; + approved_quantity: number; + total_approved_order_amount: number; + approval: string; + completed_status: string; + received: boolean; + received_by: string; + }; + + let allRows: PurchaseOrderDisplay[] = []; + + type columns = { + key: string; + title: string; + }; + + const columns: columns[] = [ + { key: "name", title: "Name" }, + { key: "purchase_order_number", title: "Purchase Order Number" }, + { key: "po_quantity", title: "PO Quantity" }, + { key: "po_status", title: "PO Status" }, + { key: "approved_vendor", title: "Approved Vendor" }, + { key: "approved_price", title: "Approved Price" }, + { key: "approved_quantity", title: "Approved Quantity" }, + { + key: "total_approved_order_amount", + title: "Total Approved Order Amount", + }, + { key: "acknowledged", title: "Acknowledged" }, + { key: "acknowledge_by", title: "Acknowledge By" }, + { key: "created_at", title: "Created At" }, + { key: "actions", title: "Actions" }, // For edit/delete buttons + ]; + + let currentPage = 1; + let rowsPerPage = 10; + $: 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; + } + + async function fetchPurchaseOrder( + filter: string | null = null, + search: string | null = null, + sort: string | null = null, + order: "asc" | "desc" = "desc", + offset: number = 0, + limit: number = 1000, + ) { + let query = supabase + .from("purchaseorder_data") + .select( + "id, purchase_order_number, villa_data, issue_id, prepared_date, po_type, po_quantity, po_status, approved_vendor, acknowledged, acknowledge_by, approved_price, approved_quantity, total_approved_order_amount, approval, completed_status, received, received_by, input_by, approved_by, created_at", + ) + .order(sort || "created_at", { ascending: order === "asc" }) + .range(offset, offset + limit - 1); + if (filter) { + query = query.eq("po_type", filter); + } + if (search) { + query = query.ilike("purchase_order_number", `%${search}%`); + } + const { data, error } = await query; + if (error) { + console.error("Error fetching purchase orders:", error); + return; + } + + // fetch issue and villa names + const issueIds = data.map((row) => row.issue_id); + const { data: issues, error: issueError } = await supabase + .from("issues") + .select("*") + .in("id", issueIds); + + if (issueError) { + console.error("Error fetching issues:", issueError); + return; + } + + const villaIds = issues.map((row) => row.villa_name).filter(Boolean); + const { data: villas, error: villaError } = await supabase + .from("villas") + .select("id, name") + .in("id", villaIds); + if (villaError) { + console.error("Error fetching villas:", villaError); + return; + } + + // masukkan villa name dan issue name ke dalam data + allRows = data.map((row) => { + const issue = issues.find((issue) => issue.id === row.issue_id); + const villa = villas.find((villa) => villa.id === issue.villa_id); + const vendor = vendors.find( + (vendor) => vendor.id === row.approved_vendor, + ); + + return { + ...row, + name: issue ? issue.name : "Unknown Issue", + villa_name: row.villa_data, + priority: issue ? issue.priority : "Unknown Priority", + approved_vendor: vendor + ? vendor.name + : "Unknown Approved Vendor", + approval: row.approval || "", + completed_status: row.completed_status || "", + } as PurchaseOrderDisplay; + }); + } + + //fetch all issues + async function fetchIssues() { + const { data, error } = await supabase + .from("issues") + .select("id, name"); + + if (error) { + console.error("Error fetching issues:", error); + return []; + } + + issues = data.map((issue) => ({ + id: issue.id, + name: issue.name, + })); + } + + async function fetchVendors() { + const { data, error } = await supabase + .from("vendor") + .select("id, name"); + + if (error) { + console.error("Error fetching vendors:", error); + return []; + } + + vendors = data.map((vendor) => ({ + id: vendor.id, + name: vendor.name, + })); + } + + onMount(() => { + fetchPurchaseOrder(); + fetchVendors(); + }); + + $: currentPage = 1; // Reset to first page when allRows changes + + let showModal = false; + let isEditing = false; + let currentEditingId: string | null = null; + let newPurchaseOrders: Record = {}; + let vendors: { id: string; name: string }[] = []; + let issues: { id: string; name: string }[] = []; + const excludedKeys = [ + "id", + "priority", + "villa_name", + "purchase_order_number", + "issue_id", + "number_project", + "input_by", + "created_at", + "actions", + "acknowledged", + "acknowledge_by", + "approval", + "completed_status", + "received", + "received_by", + "approved_quantity", + "total_approved_order_amount", + "approved_by", + "name", + "po_status", + ]; + const formColumns = columns.filter( + (col) => !excludedKeys.includes(col.key), + ); + + async function openModal(purchase: PurchaseOrderDisplay | null = null) { + await fetchIssues(); + if (purchase) { + isEditing = true; + currentEditingId = purchase.id; + newPurchaseOrders = { ...purchase }; + } else { + isEditing = false; + currentEditingId = null; + newPurchaseOrders = {}; + } + showModal = true; + } + + async function saveProject() { + purchaseOrderInsert = { + issue_id: newPurchaseOrders.issue_id || "", + prepared_date: newPurchaseOrders.prepared_date || "", + po_type: newPurchaseOrders.po_type || "", + po_quantity: newPurchaseOrders.po_quantity || 0, + po_status: newPurchaseOrders.po_status || "REQUESTED", + approved_vendor: newPurchaseOrders.approved_vendor || "", + acknowledge_by: newPurchaseOrders.acknowledge_by || "", + approved_price: newPurchaseOrders.approved_price || "", + approved_by: newPurchaseOrders.approved_by || "", + completed_status: newPurchaseOrders.completed_status || "", + }; + + if (isEditing && currentEditingId) { + const { data, error } = await supabase + .from("purchase_orders") + .update(purchaseOrderInsert) + .eq("id", currentEditingId); + + if (error) { + console.error("Error updating purchase order:", error); + return; + } + } else { + const { data, error } = await supabase + .from("purchase_orders") + .insert(purchaseOrderInsert); + + if (error) { + console.error("Error inserting purchase order:", error); + return; + } + } + + await fetchPurchaseOrder(); + showModal = false; + } + + async function deleteProject(id: string) { + const { error } = await supabase + .from("purchase_orders") + .delete() + .eq("id", id); + + if (error) { + console.error("Error deleting project:", error); + return; + } + + await fetchPurchaseOrder(); + } + + const statusOptions = [ + { label: "Requested", value: "REQUESTED", color: "#e5e7eb" }, + { label: "Prepared", value: "PREPARED", color: "#93c5fd" }, + { label: "Approved", value: "APPROVED", color: "#34d399" }, + { label: "Acknowledged", value: "ACKNOWLEDGE", color: "#60a5fa" }, + { + label: "Received - Incomplete", + value: "RECEIVE - INCOMPLETE", + color: "#fb923c", + }, + { + label: "Received - Completed", + value: "RECEIVE COMPLETED", + color: "#10b981", + }, + { label: "Canceled", value: "CANCELED", color: "#f87171" }, + ]; + + function getStatusOption(value: string) { + return statusOptions.find((option) => option.value === value) ?? null; + } + + //validate input fields purchase order + function validateInput() { + const requiredFields = [ + "prepared_date", + "po_type", + "po_quantity", + "approved_vendor", + ]; + for (const field of requiredFields) { + if (!newPurchaseOrders[field]) { + alert(`Please fill in the ${field} field.`); + return false; + } + } + return true; + } + + function validateInputApproval() { + const requiredFields = ["approved_price"]; + for (const field of requiredFields) { + if (!newPurchaseOrders[field]) { + alert(`Please fill in the ${field} field.`); + return false; + } + } + return true; + } + + async function updatePurchaseOrderStatus( + e: Event, + id: string, + row: PurchaseOrderDisplay, + ) { + const selectedOption = (e.target as HTMLSelectElement)?.value ?? ""; + const option = getStatusOption(selectedOption); + + newPurchaseOrders = { + ...row, + po_status: option?.value || row.po_status, + }; + + if (option?.value === "APPROVED") { + if (!validateInput()) { + e.preventDefault(); + return; + } + } + + const { data, error } = await supabase + .from("purchase_orders") + .update({ po_status: newPurchaseOrders.po_status }) + .eq("id", id); + + if (error) { + console.error("Error updating purchase order status:", error); + return; + } + + await fetchPurchaseOrder(); + } + + async function acknowledgedOk(id: string, status: boolean) { + const { data, error } = await supabase + .from("purchase_orders") + .update({ acknowledged: status }) + .eq("id", id); + + if (error) { + console.error("Error acknowledging purchase order:", error); + return; + } + + await fetchPurchaseOrder(); + } + + async function receivedOk(id: string, status: boolean) { + const { data, error } = await supabase + .from("purchase_orders") + .update({ receivedOk: status }) + .eq("id", id); + + if (error) { + console.error("Error acknowledging purchase order:", error); + return; + } + + await fetchPurchaseOrder(); + } + + async function updatePurchaseOrderApprovalStatus( + e: Event, + id: string, + row: PurchaseOrderDisplay, + ) { + const selectedOption = (e.target as HTMLSelectElement)?.value ?? ""; + const option = getStatusOption(selectedOption); + + newPurchaseOrders = { + ...row, + approval: option?.value || row.approval, + }; + + if (option?.value === "APPROVED") { + if (!validateInputApproval()) { + e.preventDefault(); + return; + } + } + + const { data, error } = await supabase + .from("purchase_orders") + .update({ approval: newPurchaseOrders.approval }) + .eq("id", id); + + if (error) { + console.error("Error updating purchase order status:", error); + return; + } + + await fetchPurchaseOrder(); + } + + async function completedStatusOk(id: string, status: string) { + const { data, error } = await supabase + .from("purchase_orders") + .update({ completed_status: status }) + .eq("id", id); + + if (error) { + console.error("Error acknowledging purchase order:", error); + return; + } + + await fetchPurchaseOrder(); + } + + +
+
+
+

+ 📦 + Purchase Order Acknowledged List +

+

+ Manage your purchase orders efficiently. You can add, edit, or + delete purchase orders as needed. +

+
+
+ { + const searchTerm = ( + e.target as HTMLInputElement + ).value.toLowerCase(); + fetchPurchaseOrder(null, searchTerm, "created_at", "desc"); + }} + /> + +
+
+
+
- -
+ + + {#each columns as col} + {#if col.key === "name"} + + {:else} + + {/if} + {/each} + + + + {#each paginatedRows as row} + + {#each columns as col} + {#if col.key === "name"} + + {:else if col.key === "approval"} + + {:else if col.key === "acknowledged"} + + {:else if col.key === "received"} + + {:else if col.key === "completed_status"} + + {:else if col.key === "actions"} + + {:else if col.key === "move_issue"} + + {:else} + + {/if} + {/each} + + {/each} + +
+ {col.title} + + {col.title} +
+ {row[col.key as keyof PurchaseOrderDisplay]} + + + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.acknowledged = isChecked; + + if (isChecked) { + // map to project + await acknowledgedOk( + row.id, + isChecked, + ); + } + }} + /> + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.received = isChecked; + + if (isChecked) { + // map to project + await receivedOk( + row.id, + isChecked, + ); + } + }} + /> + + + + + + + + + {row[ + col.key as keyof PurchaseOrderDisplay + ]}
+
+ + +
+
+ Showing {(currentPage - 1) * rowsPerPage + 1}– + {Math.min(currentPage * rowsPerPage, allRows.length)} of {allRows.length} +
+
+ + {#each Array(totalPages) + .fill(0) + .map((_, i) => i + 1) as page} + + {/each} + +
+
+
+ +{#if showModal} +
+
+

+ {isEditing ? "Edit Project" : "Add Project"} +

+
+ +
+ + +
+ {#each formColumns as col} + {#if col.key === "po_status"} +
+ + +
+ {:else if col.key === "po_type"} +
+ + +
+ {:else if col.key === "prepared_date"} +
+ + +
+ {:else if col.key === "po_quantity"} +
+ + +
+ {:else if col.key === "approved_price"} +
+ + +
+ {:else if col.key === "approved_vendor"} +
+ + +
+ {:else} +
+ + +
+ {/if} + {/each} +
+ + +
+
+
+
+{/if} diff --git a/src/routes/backoffice/purchaseorder/approval/+page.svelte b/src/routes/backoffice/purchaseorder/approval/+page.svelte new file mode 100644 index 0000000..12bf7fa --- /dev/null +++ b/src/routes/backoffice/purchaseorder/approval/+page.svelte @@ -0,0 +1,949 @@ + + +
+
+
+

+ 📦 + Purchase Order List +

+

+ Manage your purchase orders efficiently. You can add, edit, or + delete purchase orders as needed. +

+
+
+ { + const searchTerm = ( + e.target as HTMLInputElement + ).value.toLowerCase(); + fetchPurchaseOrder(null, searchTerm, "created_at", "desc"); + }} + /> + +
+
+
+ + + + {#each columns as col} + {#if col.key === "name"} + + {:else} + + {/if} + {/each} + + + + {#each paginatedRows as row} + + {#each columns as col} + {#if col.key === "name"} + + {:else if col.key === "approval"} + + {:else if col.key === "acknowledged"} + + {:else if col.key === "received"} + + {:else if col.key === "completed_status"} + + {:else if col.key === "actions"} + + {:else if col.key === "move_issue"} + + {:else} + + {/if} + {/each} + + {/each} + +
+ {col.title} + + {col.title} +
+ {row[col.key as keyof PurchaseOrderDisplay]} + + + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.acknowledged = isChecked; + + if (isChecked) { + // map to project + await acknowledgedOk( + row.id, + isChecked, + ); + } + }} + /> + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.received = isChecked; + + if (isChecked) { + // map to project + await receivedOk( + row.id, + isChecked, + ); + } + }} + /> + + + + + + + + + {row[ + col.key as keyof PurchaseOrderDisplay + ]}
+
+ + +
+
+ Showing {(currentPage - 1) * rowsPerPage + 1}– + {Math.min(currentPage * rowsPerPage, allRows.length)} of {allRows.length} +
+
+ + {#each Array(totalPages) + .fill(0) + .map((_, i) => i + 1) as page} + + {/each} + +
+
+
+ +{#if showModal} +
+
+

+ {isEditing ? "Edit Project" : "Add Project"} +

+
+ +
+ + +
+ {#each formColumns as col} + {#if col.key === "po_status"} +
+ + +
+ {:else if col.key === "po_type"} +
+ + +
+ {:else if col.key === "prepared_date"} +
+ + +
+ {:else if col.key === "po_quantity"} +
+ + +
+ {:else if col.key === "approved_price"} +
+ + +
+ {:else if col.key === "approved_vendor"} +
+ + +
+ {:else} +
+ + +
+ {/if} + {/each} +
+ + +
+
+
+
+{/if} diff --git a/src/routes/backoffice/purchaseorder/complete/+page.svelte b/src/routes/backoffice/purchaseorder/complete/+page.svelte new file mode 100644 index 0000000..e9d4e0b --- /dev/null +++ b/src/routes/backoffice/purchaseorder/complete/+page.svelte @@ -0,0 +1,949 @@ + + +
+
+
+

+ 📦 + Purchase Order Complete List +

+

+ Manage your purchase orders efficiently. You can add, edit, or + delete purchase orders as needed. +

+
+
+ { + const searchTerm = ( + e.target as HTMLInputElement + ).value.toLowerCase(); + fetchPurchaseOrder(null, searchTerm, "created_at", "desc"); + }} + /> + +
+
+
+ + + + {#each columns as col} + {#if col.key === "name"} + + {:else} + + {/if} + {/each} + + + + {#each paginatedRows as row} + + {#each columns as col} + {#if col.key === "name"} + + {:else if col.key === "approval"} + + {:else if col.key === "acknowledged"} + + {:else if col.key === "received"} + + {:else if col.key === "completed_status"} + + {:else if col.key === "actions"} + + {:else if col.key === "move_issue"} + + {:else} + + {/if} + {/each} + + {/each} + +
+ {col.title} + + {col.title} +
+ {row[col.key as keyof PurchaseOrderDisplay]} + + + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.acknowledged = isChecked; + + if (isChecked) { + // map to project + await acknowledgedOk( + row.id, + isChecked, + ); + } + }} + /> + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.received = isChecked; + + if (isChecked) { + // map to project + await receivedOk( + row.id, + isChecked, + ); + } + }} + /> + + + + + + + + + {row[ + col.key as keyof PurchaseOrderDisplay + ]}
+
+ + +
+
+ Showing {(currentPage - 1) * rowsPerPage + 1}– + {Math.min(currentPage * rowsPerPage, allRows.length)} of {allRows.length} +
+
+ + {#each Array(totalPages) + .fill(0) + .map((_, i) => i + 1) as page} + + {/each} + +
+
+
+ +{#if showModal} +
+
+

+ {isEditing ? "Edit Project" : "Add Project"} +

+
+ +
+ + +
+ {#each formColumns as col} + {#if col.key === "po_status"} +
+ + +
+ {:else if col.key === "po_type"} +
+ + +
+ {:else if col.key === "prepared_date"} +
+ + +
+ {:else if col.key === "po_quantity"} +
+ + +
+ {:else if col.key === "approved_price"} +
+ + +
+ {:else if col.key === "approved_vendor"} +
+ + +
+ {:else} +
+ + +
+ {/if} + {/each} +
+ + +
+
+
+
+{/if} diff --git a/src/routes/backoffice/purchaseorder/received/+page.svelte b/src/routes/backoffice/purchaseorder/received/+page.svelte new file mode 100644 index 0000000..cac99ca --- /dev/null +++ b/src/routes/backoffice/purchaseorder/received/+page.svelte @@ -0,0 +1,949 @@ + + +
+
+
+

+ 📦 + Purchase Order Received List +

+

+ Manage your purchase orders efficiently. You can add, edit, or + delete purchase orders as needed. +

+
+
+ { + const searchTerm = ( + e.target as HTMLInputElement + ).value.toLowerCase(); + fetchPurchaseOrder(null, searchTerm, "created_at", "desc"); + }} + /> + +
+
+
+ + + + {#each columns as col} + {#if col.key === "name"} + + {:else} + + {/if} + {/each} + + + + {#each paginatedRows as row} + + {#each columns as col} + {#if col.key === "name"} + + {:else if col.key === "approval"} + + {:else if col.key === "acknowledged"} + + {:else if col.key === "received"} + + {:else if col.key === "completed_status"} + + {:else if col.key === "actions"} + + {:else if col.key === "move_issue"} + + {:else} + + {/if} + {/each} + + {/each} + +
+ {col.title} + + {col.title} +
+ {row[col.key as keyof PurchaseOrderDisplay]} + + + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.acknowledged = isChecked; + + if (isChecked) { + // map to project + await acknowledgedOk( + row.id, + isChecked, + ); + } + }} + /> + + { + const isChecked = ( + e.target as HTMLInputElement + ).checked; + row.received = isChecked; + + if (isChecked) { + // map to project + await receivedOk( + row.id, + isChecked, + ); + } + }} + /> + + + + + + + + + {row[ + col.key as keyof PurchaseOrderDisplay + ]}
+
+ + +
+
+ Showing {(currentPage - 1) * rowsPerPage + 1}– + {Math.min(currentPage * rowsPerPage, allRows.length)} of {allRows.length} +
+
+ + {#each Array(totalPages) + .fill(0) + .map((_, i) => i + 1) as page} + + {/each} + +
+
+
+ +{#if showModal} +
+
+

+ {isEditing ? "Edit Project" : "Add Project"} +

+
+ +
+ + +
+ {#each formColumns as col} + {#if col.key === "po_status"} +
+ + +
+ {:else if col.key === "po_type"} +
+ + +
+ {:else if col.key === "prepared_date"} +
+ + +
+ {:else if col.key === "po_quantity"} +
+ + +
+ {:else if col.key === "approved_price"} +
+ + +
+ {:else if col.key === "approved_vendor"} +
+ + +
+ {:else} +
+ + +
+ {/if} + {/each} +
+ + +
+
+
+
+{/if} diff --git a/src/routes/backoffice/timesheets/+page.svelte b/src/routes/backoffice/timesheets/+page.svelte index 7a4b177..a24b4e5 100644 --- a/src/routes/backoffice/timesheets/+page.svelte +++ b/src/routes/backoffice/timesheets/+page.svelte @@ -122,17 +122,37 @@ { key: "actions", title: "Actions" }, ]; - async function fetchTimeSheets() { - const { data: timesheet, error: timesheetError } = await supabase + async function fetchTimeSheets( + filter: string | null = null, + searchTerm: string | null = null, + sortColumn: string | null = "created_at", + sortOrder: "asc" | "desc" = "desc", + offset: number = 0, + limit: number = 10, + ) { + let query = supabase .from("timesheets") - .select("*") - .order("created_at", { ascending: false }); - - if (timesheetError) { - console.error("Error fetching issues:", timesheetError); + .select("*", { count: "exact" }) + .order(sortColumn || "created_at", { + ascending: sortOrder === "asc", + }); + if (filter) { + query = query.eq("category_of_work", filter); + } + if (searchTerm) { + query = query.ilike("name", `%${searchTerm}%`); + } + 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; } - // Ambil semua villa_id unik dari issues const villaIds = [ ...new Set(timesheet.map((i: Timesheets) => i.villa_id)), @@ -328,19 +348,54 @@
-
+
-

Timesheet List

+

+ 🕒 Timesheet List +

Manage and track timesheets for staff members.

- +
+ { + const searchTerm = ( + e.target as HTMLInputElement + ).value.toLowerCase(); + fetchTimeSheets(null, searchTerm, "created_at", "desc"); + }} + /> + + + +