diff --git a/src/routes/backoffice/issue/+page.svelte b/src/routes/backoffice/issue/+page.svelte index ed1c0c4..4aed7b1 100644 --- a/src/routes/backoffice/issue/+page.svelte +++ b/src/routes/backoffice/issue/+page.svelte @@ -21,7 +21,6 @@ { label: "Guest", value: "Guest" }, { label: "Agent", value: "Agent" }, ]; - const issueTypes = [ { label: "Facilities - Air Conditioner", value: "Facilities - Air Conditioner" }, { label: "Facilities - Appliances", value: "Facilities - Appliances" }, @@ -83,18 +82,6 @@ { label: "Other - Payment", value: "Other - Payment" }, { label: "Other - Transport", value: "Other - Transport" }, ]; - - type PurchaseOrder = { - villa_id: string; - issue_id: string; - po_status: string; - requested_by: string; - requested_date: string; - po_due: string; - po_item: string; - po_quantity: string; - po_type: string; - }; const areaOfVilla = [ { label: "All Bathrooms", value: "All Bathrooms" }, { label: "All Guest Houses", value: "All Guest Houses" }, @@ -149,147 +136,6 @@ { label: "TV Room", value: "TV Room" }, { label: "Water Feature", value: "Water Feature" } ]; - - type Villa = { - id: string; - villa_name: string; - }; - - type User = { - id: string; - employee_name: string; - }; - let currentVillaFilter: string | null = null; - let currentSearchTerm: string | null = null; - let showProjectModal = false; - let selectedIssueId: string | null = null; - let showPurchaseOrderModal = false; - let newPO: PurchaseOrder = { - villa_id: "", - issue_id: "", - po_status: "requested", - requested_by: "", - requested_date: "", - po_due: "", - po_item: "", - po_quantity: "", - po_type: "" - }; - let newProject = { - project_name: "", - issue_id: "", - input_by: "", - project_due_date: "", - assigned_to: "", - project_comment: "" - }; - - let dataVilla: Villa[] = []; - let dataUser: User[] = []; - - let projectIssueMap: Set = new Set(); - let purchaseOrderMap: Set = new Set(); - - async function fetchExistingProjectLinks() { - const { data, error } = await supabase - .from("vb_projects") - .select("issue_id"); - - if (!error && data) { - projectIssueMap = new Set(data.map((p) => p.issue_id)); - } else { - console.error("Error loading existing projects:", error); - } - } - async function fetchExistingPurchaseOrders() { - const { data } = await supabase.from("vb_purchase_orders").select("issue_id"); - if (data) purchaseOrderMap = new Set(data.map(p => p.issue_id)); - } - - onMount(async () => { - await fetchExistingProjectLinks(); - const { data, error } = await supabase - .from("vb_villas") - .select("id, villa_name, villa_status") - .eq("villa_status", "Active"); - - if (error) { - console.error("Error fetching villas:", error); - } else if (data) { - dataVilla = data; - } - - const { data: userData, error: userError } = await supabase - .from("vb_employee") - .select("id, employee_name, employee_status") - .eq("employee_status", "Active"); - if (userError) { - console.error("Error fetching users:", userError); - } else if (userData) { - dataUser = userData; - } - }); - - type Issue = { - id: string; - villa_id: string; - villa_name: string; - area_of_villa: string; - priority: string; - issue_type: string; - issue_number: string; - move_issue: string; - description_of_the_issue: string; - reported_date: string; - issue_related_image: string; - issue_source: string; - reported_by: string; - input_by: string; - guest_communication: string; - resolution: string; - need_approval: boolean; - created_at: string; - updated_by?: string; - updated_at?: string; - }; - - type issueInsert = { - villa_id: string; - area_of_villa: string; - priority: string; - issue_type: string; - description_of_the_issue: string; - reported_date: string; - issue_related_image: string; - issue_source: string; - reported_by: string; - input_by: string; - guest_communication: string; - resolution: string; - need_approval: boolean; - }; - - type POItem = { - item_name: string; - }; - - let poItems: POItem[] = []; - - async function fetchPoItems() { - const { data } = await supabase.from("vb_po_item").select("item_name"); - if (data) poItems = data; - } - let allRows: Issue[] = []; - let offset = 0; - let limit = 10; - let totalItems = 0; - export let formErrors = writable<{ [key: string]: string }>({}); - - type columns = { - key: string; - title: string; - }; - const columns: columns[] = [ { key: "description_of_the_issue", title: "Description of The Issue" }, { key: "issue_number", title: "Issue Number" }, @@ -314,7 +160,165 @@ { key: "updated_at", title: "Updated At" }, { key: "actions", title: "Actions" }, ]; + const excludedKeys = [ + "id", + "created_at", + "move_issue", + "issue_number", + "actions", + "villa_name", + "reported_name", + "inputed_name", + "updated_name", + "updated_at", + "updated_by", + ]; + const excludedKeysDisplay = [ + "villa_id", + "reported_by", + "input_by", + "updated_by", + ]; + const formColumns = columns.filter( + (col) => !excludedKeys.includes(col.key), + ); + const formColumnsDisplay = columns.filter( + (col) => !excludedKeysDisplay.includes(col.key), + ); + + // Define types for the data structures + type PurchaseOrder = { + villa_id: string; + issue_id: string; + po_status: string; + requested_by: string; + requested_date: string; + po_due: string; + po_item: string; + po_quantity: string; + po_type: string; + }; + type Villa = { + id: string; + villa_name: string; + }; + type User = { + id: string; + employee_name: string; + }; + type Issue = { + id: string; + villa_id: string; + villa_name: string; + area_of_villa: string; + priority: string; + issue_type: string; + issue_number: string; + move_issue: string; + description_of_the_issue: string; + reported_date: string; + issue_related_image: string; + issue_source: string; + reported_by: string; + input_by: string; + guest_communication: string; + resolution: string; + need_approval: boolean; + created_at: string; + updated_by?: string; + updated_at?: string; + }; + type issueInsert = { + villa_id: string; + area_of_villa: string; + priority: string; + issue_type: string; + description_of_the_issue: string; + reported_date: string; + issue_related_image: string; + issue_source: string; + reported_by: string; + input_by: string; + guest_communication: string; + resolution: string; + need_approval: boolean; + }; + type POItem = { + item_name: string; + }; + type columns = { + key: string; + title: string; + }; + + // Reactive variables + let currentVillaFilter: string | null = null; + let currentSearchTerm: string | null = null; + let showProjectModal = false; + let selectedIssueId: string | null = null; + let showPurchaseOrderModal = false; + let dataVilla: Villa[] = []; + let dataUser: User[] = []; + let projectIssueMap: Set = new Set(); + let purchaseOrderMap: Set = new Set(); + let poItems: POItem[] = []; + let newPO: PurchaseOrder = { + villa_id: "", + issue_id: "", + po_status: "requested", + requested_by: "", + requested_date: "", + po_due: "", + po_item: "", + po_quantity: "", + po_type: "" + }; + let newProject = { + project_name: "", + issue_id: "", + input_by: "", + project_due_date: "", + assigned_to: "", + project_comment: "" + }; + let allRows: Issue[] = []; + let showModal = false; + let isEditing = false; + let selectedFile: File | null = null; + let imagePreviewUrl: string | null = null; + let currentEditingId: string | null = null; + let newIssue: Record = {}; + let selectedIssueSummary = ""; + let offset = 0; + let limit = 10; + let totalItems = 0; + let currentPage = offset + 1; + let rowsPerPage = limit; + $: totalPages = Math.ceil(totalItems / rowsPerPage); + + // Fetch existing project links and purchase orders + async function fetchExistingProjectLinks() { + const { data, error } = await supabase + .from("vb_projects") + .select("issue_id"); + if (!error && data) { + projectIssueMap = new Set(data.map((p) => p.issue_id)); + } else { + console.error("Error loading existing projects:", error); + } + } + // Fetch existing purchase orders + async function fetchExistingPurchaseOrders() { + const { data } = await supabase.from("vb_purchase_orders").select("issue_id"); + if (data) purchaseOrderMap = new Set(data.map(p => p.issue_id)); + } + // Fetch PO items + async function fetchPoItems() { + const { data } = await supabase.from("vb_po_item").select("item_name"); + if (data) poItems = data; + } + // Fetch issues with optional filters async function fetchIssues( search: string | null = null, villaNameFilter: string | null = null, @@ -358,71 +362,7 @@ // Gabungkan data villa ke dalam setiap issue allRows = issues; } - - let currentPage = offset + 1; - let rowsPerPage = limit; - $: totalPages = Math.ceil(totalItems / rowsPerPage); - - function goToPage(page: number) { - if (page >= 1 && page <= totalPages) currentPage = page; - } - - onMount(() => { - fetchIssues(null, null, "created_at", "desc", offset, limit); - fetchPoItems(); - fetchExistingPurchaseOrders(); - }); - - let showModal = false; - let isEditing = false; - let currentEditingId: string | null = null; - let newIssue: Record = {}; - const excludedKeys = [ - "id", - "created_at", - "move_issue", - "issue_number", - "actions", - "villa_name", - "reported_name", - "inputed_name", - "updated_name", - "updated_at", - "updated_by", - ]; - - const excludedKeysDisplay = [ - "villa_id", - "reported_by", - "input_by", - "updated_by", - ]; - const formColumns = columns.filter( - (col) => !excludedKeys.includes(col.key), - ); - - const formColumnsDisplay = columns.filter( - (col) => !excludedKeysDisplay.includes(col.key), - ); - - function openModal(issue?: Record) { - if (issue) { - if (projectIssueMap.has(issue.id)) { - alert("This issue is linked to a project and cannot be edited."); - return; - } - isEditing = true; - currentEditingId = issue.id; - newIssue = { ...issue }; - } else { - isEditing = false; - currentEditingId = null; - newIssue = {}; - } - showModal = true; - } - - + // Function to handle form submission and save issue async function saveIssue(event: Event) { const session = await supabase.auth.getSession(); const userId = session.data.session?.user.id; @@ -539,13 +479,12 @@ selectedFile = null; } - // function get public URL for image supabase async function getPublicUrl(path: string): Promise { const { data } = supabase.storage.from("villabugis").getPublicUrl(path); return data.publicUrl; } - + // Function to handle issue deletion async function deleteIssue(id: string) { if (projectIssueMap.has(id)) { alert("This issue is linked to a project and cannot be deleted."); @@ -565,165 +504,7 @@ alert("Issue deleted."); } } - - - let selectedFile: File | null = null; - let imagePreviewUrl: string | null = null; - - function handleFileChange(event: Event) { - const input = event.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - selectedFile = input.files[0]; - imagePreviewUrl = URL.createObjectURL(selectedFile); - } - } - - function validateForm(formData: FormData): boolean { - const errors: { [key: string]: string } = {}; - const requiredFields = [ - "description_of_the_issue", - "issue_source", - "villa_id", - "reported_date", - "reported_by", - "priority", - "issue_type", - "input_by", - "area_of_villa", - ]; - - 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 errorClass(field: string): string { - return $formErrors[field] ? "border-red-500" : "border"; - } - - // insert id issue to project - async function moveIssueToProject(issueId: string) { - // get user id from session - const session = await supabase.auth.getSession(); - if (!session) { - console.error("User not authenticated"); - return; - } - - const userId = session.data.session?.user.id; - - // update move_issue field in the issue - const { error: updateError } = await supabase - .from("vb_issues") - .update({ - move_issue: "PROJECT", - updated_by: userId, - updated_at: new Date().toISOString(), - }) - .eq("id", issueId); - - if (updateError) { - console.error("Error updating issue move_issue:", updateError); - return; - } - - const { error } = await supabase - .from("vb_projects") - .insert({ issue_id: issueId }); - - if (error) { - console.error("Error moving issue to project:", error); - return; - } - alert(`Issue ${issueId} moved to project successfully.`); - - await fetchIssues(); - } - - // open project modal - let selectedIssueSummary = ""; - - function openProjectModal(issue: Issue) { - const dueDate = new Date(issue.created_at); - dueDate.setDate(dueDate.getDate() + 2); - - selectedIssueSummary = issue.description_of_the_issue ?? ""; - - newProject = { - project_name: "", - issue_id: issue.id, - input_by: "", // can be prefilled if desired - project_due_date: dueDate.toISOString().split("T")[0], // mm/dd/yyyy - assigned_to: "", - project_comment: "" - }; - - showProjectModal = true; - } - - function openPurchaseOrderModal(issue) { - if (purchaseOrderMap.has(issue.id)) { - alert("This issue already has a purchase order."); - return; - } - - const today = new Date(); - const dueDate = new Date(today); - dueDate.setDate(dueDate.getDate() + 2); - - const formatDate = (d) => d.toISOString().split("T")[0]; - - newPO = { - issue_id: issue.id, - villa_id: issue.villa_id, - po_status: "requested", - requested_by: "", - requested_date: formatDate(today), - po_due: formatDate(dueDate), - po_item: "", - po_quantity: "", - po_type: "" - }; - - selectedIssueSummary = issue.description_of_the_issue ?? ""; - showPurchaseOrderModal = true; - } - async function submitPurchaseOrder() { - if (!newPO.requested_by || !newPO.po_item || !newPO.po_quantity || !newPO.po_type) { - alert("Please complete all required fields."); - return; - } - - const { data: existing } = await supabase - .from("vb_purchase_orders") - .select("id") - .eq("issue_id", newPO.issue_id) - .maybeSingle(); - - if (existing) { - alert("Purchase order already exists for this issue."); - return; - } - - const { error } = await supabase.from("vb_purchase_orders").insert(newPO); - - if (error) { - console.error(error); - alert("Failed to create PO."); - return; - } - - // reactively update map - purchaseOrderMap = new Set([...purchaseOrderMap, newPO.issue_id]); - - alert("Purchase Order submitted!"); - showPurchaseOrderModal = false; - } + // Function to submit project async function submitProject() { if (!newProject.project_name || !newProject.input_by || !newProject.assigned_to) { alert("Please fill all required fields"); @@ -776,45 +557,178 @@ alert("Project created successfully."); showProjectModal = false; } - - - - // insert id issue to purchase order - async function moveIssueToPurchaseOrder(issueId: string) { - // get user id from session - const session = await supabase.auth.getSession(); - if (!session) { - console.error("User not authenticated"); + // Function to submit purchase order + async function submitPurchaseOrder() { + if (!newPO.requested_by || !newPO.po_item || !newPO.po_quantity || !newPO.po_type) { + alert("Please complete all required fields."); return; } - const userId = session.data.session?.user.id; - // update move_issue field in the issue - const { error: updateError } = await supabase - .from("vb_issues") - .update({ - move_issue: "PURCHASE_ORDER", - updated_by: userId, - updated_at: new Date().toISOString(), - }) - .eq("id", issueId); - if (updateError) { - console.error("Error updating issue move_issue:", updateError); - return; - } - - const { error } = await supabase + const { data: existing } = await supabase .from("vb_purchase_orders") - .insert({ issue_id: issueId }); + .select("id") + .eq("issue_id", newPO.issue_id) + .maybeSingle(); + + if (existing) { + alert("Purchase order already exists for this issue."); + return; + } + + const { error } = await supabase.from("vb_purchase_orders").insert(newPO); if (error) { - console.error("Error moving issue to purchase order:", error); + console.error(error); + alert("Failed to create PO."); return; } - alert(`Issue ${issueId} moved to purchase order successfully.`); - await fetchIssues(); + // reactively update map + purchaseOrderMap = new Set([...purchaseOrderMap, newPO.issue_id]); + + alert("Purchase Order submitted!"); + showPurchaseOrderModal = false; } + + // Function to open purchase order modal + function openPurchaseOrderModal(issue) { + if (purchaseOrderMap.has(issue.id)) { + alert("This issue already has a purchase order."); + return; + } + + const today = new Date(); + const dueDate = new Date(today); + dueDate.setDate(dueDate.getDate() + 2); + + const formatDate = (d) => d.toISOString().split("T")[0]; + + newPO = { + issue_id: issue.id, + villa_id: issue.villa_id, + po_status: "requested", + requested_by: "", + requested_date: formatDate(today), + po_due: formatDate(dueDate), + po_item: "", + po_quantity: "", + po_type: "" + }; + + selectedIssueSummary = issue.description_of_the_issue ?? ""; + showPurchaseOrderModal = true; + } + // Function to open project modal + function openProjectModal(issue: Issue) { + const dueDate = new Date(issue.created_at); + dueDate.setDate(dueDate.getDate() + 2); + + selectedIssueSummary = issue.description_of_the_issue ?? ""; + + newProject = { + project_name: "", + issue_id: issue.id, + input_by: "", // can be prefilled if desired + project_due_date: dueDate.toISOString().split("T")[0], // mm/dd/yyyy + assigned_to: "", + project_comment: "" + }; + + showProjectModal = true; + } + // Pagination functions + function goToPage(page: number) { + if (page >= 1 && page <= totalPages) currentPage = page; + } + // Function to handle page change + function openModal(issue?: Record) { + if (issue) { + if (projectIssueMap.has(issue.id)) { + alert("This issue is linked to a project and cannot be edited."); + return; + } + isEditing = true; + currentEditingId = issue.id; + newIssue = { ...issue }; + } else { + isEditing = false; + currentEditingId = null; + newIssue = {}; + } + showModal = true; + } + // Function to handle file input change + function handleFileChange(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + selectedFile = input.files[0]; + imagePreviewUrl = URL.createObjectURL(selectedFile); + } + } + // Function to validate form data + function validateForm(formData: FormData): boolean { + const errors: { [key: string]: string } = {}; + const requiredFields = [ + "description_of_the_issue", + "issue_source", + "villa_id", + "reported_date", + "reported_by", + "priority", + "issue_type", + "input_by", + "area_of_villa", + ]; + + 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 get error class for form fields + function errorClass(field: string): string { + return $formErrors[field] ? "border-red-500" : "border"; + } + + + // Fetch villas and users on mount + onMount(async () => { + await fetchExistingProjectLinks(); + const { data, error } = await supabase + .from("vb_villas") + .select("id, villa_name, villa_status") + .eq("villa_status", "Active"); + + if (error) { + console.error("Error fetching villas:", error); + } else if (data) { + dataVilla = data; + } + + const { data: userData, error: userError } = await supabase + .from("vb_employee") + .select("id, employee_name, employee_status") + .eq("employee_status", "Active"); + if (userError) { + console.error("Error fetching users:", userError); + } else if (userData) { + dataUser = userData; + } + }); + + // Fetch issues, PO items, and existing purchase orders on mount + onMount(() => { + fetchIssues(null, null, "created_at", "desc", offset, limit); + fetchPoItems(); + fetchExistingPurchaseOrders(); + }); + + // Reactive store for form errors + export let formErrors = writable<{ [key: string]: string }>({});
diff --git a/src/routes/backoffice/timesheets/+page.svelte b/src/routes/backoffice/timesheets/+page.svelte index b2bd37f..b832b37 100644 --- a/src/routes/backoffice/timesheets/+page.svelte +++ b/src/routes/backoffice/timesheets/+page.svelte @@ -26,7 +26,6 @@ approved_date?: Date; created_at?: Date; }; - type TimesheetDisplay = { id: number; name: string; @@ -50,7 +49,6 @@ remarks: string; created_at?: Date; }; - type TimesheetsInsert = { entered_by: string; work_description: string; @@ -70,6 +68,15 @@ remarks: string; approval: boolean | null; // Allow null for new entries }; + type Villa = { + id: string; + villa_name: string; + }; + type columns = { + key: string; + title: string; + }; + const categoryOfWork = [ { label: "Cleaning", value: "Cleaning" }, @@ -80,52 +87,11 @@ { label: "Administration", value: "Administration" }, { label: "Non Billable", value: "Non Billable" }, ]; - const typeOfWork = [ { label: "Running", value: "Running" }, { label: "Periodic", value: "Periodic" }, { label: "Irregular", value: "Irregular" }, ]; - - let currentUserId: string | null = null; - let currentVillaFilter: string | null = null; - let currentSearchTerm: string | null = null; - - onMount(async () => { - const { - data: { user }, - } = await supabase.auth.getUser(); - - currentUserId = user?.id ?? null; - }); - - type Villa = { - id: string; - villa_name: string; - }; - - let dataVilla: Villa[] = []; - - onMount(async () => { - const { data, error } = await supabase - .from("vb_villas") - .select("id, villa_name") - .eq("villa_status", "Active"); - - if (error) { - console.error("Error fetching villas:", error); - } else if (data) { - dataVilla = data; - } - }); - - let allRows: TimesheetDisplay[] = []; - - type columns = { - key: string; - title: string; - }; - const columns: columns[] = [ { key: "name", title: "Work Description" }, { key: "staff_id", title: "Staff Name" }, @@ -142,6 +108,35 @@ { key: "created_at", title: "Created At" }, { key: "actions", title: "Actions" }, ]; + + // Store for current user ID and filters + let currentUserId: string | null = null; + let currentVillaFilter: string | null = null; + let currentSearchTerm: string | null = null; + let dataVilla: Villa[] = []; + let allRows: TimesheetDisplay[] = []; + + onMount(async () => { + const { + data: { user }, + } = await supabase.auth.getUser(); + + currentUserId = user?.id ?? null; + }); + onMount(async () => { + const { data, error } = await supabase + .from("vb_villas") + .select("id, villa_name") + .eq("villa_status", "Active"); + + if (error) { + console.error("Error fetching villas:", error); + } else if (data) { + dataVilla = data; + } + }); + + // Function to calculate total work hours function calculateTotalHours() { if (form.datetime_in && form.datetime_out) { const start = new Date(form.datetime_in); @@ -154,13 +149,14 @@ } } + // Function to fetch timesheets with optional filters and sorting async function fetchTimeSheets( villaNameFilter: string | null = null, searchTerm: string | null = null, sortColumn: string | null = "created_at", sortOrder: "asc" | "desc" = "desc", offset: number = 0, - limit: number = 10, + limit: number = 1000, ) { let query = supabase .from("vb_timesheet") @@ -269,7 +265,7 @@ // Sort the rows based on the sortColumn and sortOrder } let currentPage = 1; - let rowsPerPage = 5; + let rowsPerPage = 20; $: totalPages = Math.ceil(allRows.length / rowsPerPage); $: paginatedRows = allRows.slice( (currentPage - 1) * rowsPerPage,