Files
vberp/src/routes/backoffice/purchaseorder/+page.svelte
2025-07-01 18:18:20 +08:00

1877 lines
72 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import { onMount } from "svelte";
import { supabase } from "$lib/supabaseClient";
import { getSessionAuthId } from "$lib/utils/authUtil";
type PurchaseOrderInsert = {
issue_id: string;
prepared_date: string;
po_type: string;
po_quantity: number;
po_price: number;
po_total_price: number;
input_by: string;
updated_by: string;
updated_at: string;
};
type PurchaseOrders = {
id: string;
purchase_order_number: string;
prepared_date: string;
po_type: string;
po_quantity: number;
po_status: string;
approved_vendor: string;
proses_to_approval: boolean;
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_price: number;
po_total_price: number;
po_status: string;
approved_vendor: string;
acknowledged: boolean;
acknowledge_by: string;
approved_by: string;
prepared: boolean | null;
approved_price: number;
approved_quantity: number;
total_approved_order_amount: number;
proses_to_approval: boolean;
approval: string;
completed_status: string;
received: boolean;
received_by: string;
created_at: string;
updated_at?: string;
updated_by?: string;
updated_name?: string;
input_by?: string;
input_name?: string;
};
type columns = {
key: string;
title: string;
};
const columns: columns[] = [
{ key: "issue_name", title: "Issue Name" },
{ key: "requested_date", title: "Requested Date" },
{ key: "purchase_order_number", title: "PO Number" },
{ key: "po_status", title: "PO Status" },
{ key: "villa_data", title: "Villa Name" },
{ key: "po_item", title: "PO Product" },
{ key: "po_remark", title: "PO Remark" },
{ key: "po_due", title: "PO Due" },
{ key: "prepared", title: "Prepare PO" },
{ key: "approved", title: "Approval" },
{ key: "acknowledged", title: "Acknowledge" },
{ key: "completed", title: "Complete" },
{ key: "received", title: "Receive" },
{ key: "updated_at", title: "Updated At" },
{ key: "payment", title: "Payment" },
{ key: "actions", title: "Actions" }, // For edit/delete buttons
];
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",
"proses_to_approval",
"approved_by",
"name",
"po_status",
"approved_quantity",
"total_approved_order_amount",
"approved_vendor",
"approved_price",
"updated_at",
"updated_by",
"updated_name",
"inputed_name",
"approved_name",
"acknowledged_name",
"received_name",
];
const formColumns = columns.filter(
(col) => !excludedKeys.includes(col.key),
);
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: "RECEIVED INCOMPLETE",
color: "#fb923c",
},
{
label: "Received - Completed",
value: "RECEIVED COMPLETED",
color: "#10b981",
},
{ label: "Canceled", value: "CANCELED", color: "#f87171" },
];
//reactive variables
let purchaseOrderInsert: PurchaseOrderInsert = {
issue_id: "",
prepared_date: "",
po_type: "",
po_quantity: 0,
po_price: 0,
po_total_price: 0,
input_by: "",
updated_by: "",
updated_at: new Date().toISOString(),
};
let showEditModal = false;
let editForm = {
po_number: "",
issue_name: "",
villa_name: "",
po_status: "",
prepared: null,
approved: null,
acknowledged: null,
completed: null,
received: null,
po_remark: ""
};
let showAddPOModal = false;
let addPOForm = {
po_type: "",
villa_id: "",
po_item: "",
po_quantity: 1,
po_status: "requested",
po_remark: "",
requested_by: "",
requested_date: new Date().toISOString().split("T")[0]
};
let showApprovalModal = false;
let approvalForm = {
po_number: "",
approval: "",
approved_by: "",
approved_date: "",
reject_comment: ""
};
let villaOptions = [];
let poItemOptions = [];
let employeeOptions = [];
let allRows: PurchaseOrderDisplay[] = [];
let currentPage = 1;
let rowsPerPage = 10;
let currentUserId = "";
let currentUserFullName = "";
let showPaymentModal = false;
let paymentForm = {
payment_method: "",
total_approved_order_amount: 0,
"1st_pay_amt": 0,
"2nd_pay_amt": 0,
"3rd_pay_amt": 0,
"4th_pay_amt": 0,
"5th_pay_amt": 0,
"6th_pay_amt": 0,
"1st_pay_date": "",
"2nd_pay_date": "",
"3rd_pay_date": "",
"4th_pay_date": "",
"5th_pay_date": "",
"6th_pay_date": "",
due_remaining: 0
};
let showAcknowledgedModal = false;
let acknowledgedForm = {
po_number: "",
acknowledged: "",
acknowledged_by: "",
acknowledged_date: ""
};
let showPreparedModal = false;
let selectedPO = null;
let preparedByOptions: any[] = [];
let vendorOptions: any[] = [];
let preparedForm = {
prepared_by: "",
prepared_date: "",
po_item: "",
po_quantity: 1,
q1_vendor: "",
q2_vendor: "",
q3_vendor: "",
q1_vendor_price: 0,
q2_vendor_price: 0,
q3_vendor_price: 0,
po_remark: "",
approved_quantity: 0,
approved_vendor: "",
approved_price: 0,
total_approved_order_amount: 0
};
let showReceivedModal = false;
let receivedForm = {
po_number: "",
received: "",
received_by: "",
received_date: ""
};
let showCompletedModal = false;
let completedForm = {
po_number: "",
completed: "",
completed_by: "",
completed_date: ""
};
let showModal = false;
let isEditing = false;
let currentEditingId: string | null = null;
let newPurchaseOrders: Record<string, any> = {};
let vendors: { id: string; name: string }[] = [];
let issues: { id: string; name: string }[] = [];
// Pagination variables
$: totalPages = Math.ceil(allRows.length / rowsPerPage);
$: paginatedRows = allRows.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage,
);
$: currentPage = 1; // Reset to first page when allRows changes
// Function to format numbers as ordinal (1st, 2nd, 3rd, etc.)
function ordinal(num: number) {
if (num === 1) return "1st";
if (num === 2) return "2nd";
if (num === 3) return "3rd";
return `${num}th`;
}
// Open edit modal for purchase order
function openEditModal(row) {
selectedPO = row;
editForm = {
po_number: row.purchase_order_number || "",
issue_name: row.issue_name || "",
villa_name: row.villa_data || "",
po_status: row.po_status || "",
prepared: row.prepared,
approved: row.approved,
acknowledged: row.acknowledged,
completed: row.completed,
received: row.received,
po_remark: row.po_remark || ""
};
showEditModal = true;
}
// pagination functions
function goToPage(page: number) {
if (page >= 1 && page <= totalPages) currentPage = page;
}
// Open approval modal for purchase order
function openApprovalModal(row) {
selectedPO = row;
approvalForm = {
po_number: row.purchase_order_number || "",
approval: row.approval ? "approve" : "reject",
approved_by: currentUserId,
approved_date: new Date().toISOString().split("T")[0],
reject_comment: row.reject_comment || ""
};
showApprovalModal = true;
}
// acknowledge purchase order
function openAcknowledgedModal(row) {
selectedPO = row;
acknowledgedForm = {
po_number: row.purchase_order_number || "",
acknowledged: row.acknowledged ? "acknowledged" : "reject",
acknowledged_by: currentUserId,
acknowledged_date: new Date().toISOString().split("T")[0]
};
showAcknowledgedModal = true;
}
// open receive modal
function openReceivedModal(row) {
selectedPO = row;
receivedForm = {
po_number: row.purchase_order_number || "",
received: row.received ? "received" : "reject",
received_by: currentUserId,
received_date: new Date().toISOString().split("T")[0]
};
showReceivedModal = true;
}
// open completed modal
function openCompletedModal(row) {
selectedPO = row;
completedForm = {
po_number: row.purchase_order_number || "",
completed: row.completed ? "completed" : "reject",
completed_by: currentUserId,
completed_date: new Date().toISOString().split("T")[0]
};
showCompletedModal = true;
}
// Update total amount in prepared form
function updateTotalAmount() {
preparedForm.total_approved_order_amount =
preparedForm.approved_quantity * preparedForm.approved_price;
}
function getStatusOption(value: string) {
return statusOptions.find((option) => option.value === value) ?? null;
}
// open payment modal for purchase order
function openPaymentModal(row) {
selectedPO = row;
paymentForm = {
payment_method: row.payment_method || "",
total_approved_order_amount: row.total_approved_order_amount || 0,
"1st_pay_amt": row["1st_pay_amt"] || 0,
"2nd_pay_amt": row["2nd_pay_amt"] || 0,
"3rd_pay_amt": row["3rd_pay_amt"] || 0,
"4th_pay_amt": row["4th_pay_amt"] || 0,
"5th_pay_amt": row["5th_pay_amt"] || 0,
"6th_pay_amt": row["6th_pay_amt"] || 0,
"1st_pay_date": row["1st_pay_date"] || "",
"2nd_pay_date": row["2nd_pay_date"] || "",
"3rd_pay_date": row["3rd_pay_date"] || "",
"4th_pay_date": row["4th_pay_date"] || "",
"5th_pay_date": row["5th_pay_date"] || "",
"6th_pay_date": row["6th_pay_date"] || "",
due_remaining: calculateDueRemaining(row)
};
showPaymentModal = true;
}
//validate input fields purchase order
function validateInput() {
const requiredFields = [
"prepared_date",
"po_type",
"po_quantity",
"po_price",
];
for (const field of requiredFields) {
if (!newPurchaseOrders[field]) {
alert(`Please fill in the ${field} field.`);
return false;
}
}
return true;
}
// Calculate remaining due amount
function calculateDueRemaining(source) {
const total = source.total_approved_order_amount || 0;
const sum =
(source["1st_pay_amt"] || 0) +
(source["2nd_pay_amt"] || 0) +
(source["3rd_pay_amt"] || 0) +
(source["4th_pay_amt"] || 0) +
(source["5th_pay_amt"] || 0) +
(source["6th_pay_amt"] || 0);
return total - sum;
}
// Update due remaining amount in payment form
function updateDueRemaining() {
paymentForm.due_remaining =
paymentForm.total_approved_order_amount -
paymentForm["1st_pay_amt"] -
paymentForm["2nd_pay_amt"] -
paymentForm["3rd_pay_amt"] -
paymentForm["4th_pay_amt"] -
paymentForm["5th_pay_amt"] -
paymentForm["6th_pay_amt"];
}
// Save payment data for purchase order
async function savePayment() {
const { error } = await supabase
.from("vb_purchase_orders")
.update({
payment_method: paymentForm.payment_method,
"1st_pay_amt": paymentForm["1st_pay_amt"],
"2nd_pay_amt": paymentForm["2nd_pay_amt"],
"3rd_pay_amt": paymentForm["3rd_pay_amt"],
"4th_pay_amt": paymentForm["4th_pay_amt"],
"5th_pay_amt": paymentForm["5th_pay_amt"],
"6th_pay_amt": paymentForm["6th_pay_amt"],
"1st_pay_date": paymentForm["1st_pay_date"] || null,
"2nd_pay_date": paymentForm["2nd_pay_date"] || null,
"3rd_pay_date": paymentForm["3rd_pay_date"] || null,
"4th_pay_date": paymentForm["4th_pay_date"] || null,
"5th_pay_date": paymentForm["5th_pay_date"] || null,
"6th_pay_date": paymentForm["6th_pay_date"] || null,
due_remaining: paymentForm.due_remaining
})
.eq("id", selectedPO.id);
if (error) {
console.error("Error saving payment data:", error);
alert("Failed to save.");
return;
}
showPaymentModal = false;
await fetchPurchaseOrder();
}
// Save approval for purchase order
async function saveApproval() {
const { error } = await supabase
.from("vb_purchase_orders")
.update({
approved: approvalForm.approval === "approve",
approved_by: approvalForm.approval === "approve" ? currentUserId : null,
approved_date: approvalForm.approved_date,
reject_comment: approvalForm.reject_comment || null,
po_status: "approved"
})
.eq("id", selectedPO.id);
if (error) {
console.error("Error saving approval:", error);
alert("Failed to save.");
return;
}
showApprovalModal = false;
await fetchPurchaseOrder();
}
// populate dropdowns for adding purchase orders
async function fetchAddPODropdowns() {
const [{ data: villas }, { data: items }, { data: employees }] = await Promise.all([
supabase.from("vb_villas").select("id, villa_name").eq("villa_status", "Active").order("villa_name", { ascending: true }),
supabase.from("vb_po_item").select("id, item_name").order("item_name", { ascending: true }),
supabase.from("vb_employee").select("id, employee_name").eq("employee_status", "Active").order("employee_name", { ascending: true }),
]);
villaOptions = villas || [];
poItemOptions = items || [];
employeeOptions = employees || [];
}
// Fetch purchase orders add
async function openAddPOModal() {
await fetchAddPODropdowns();
addPOForm = {
po_type: "",
villa_id: "",
po_item: "",
po_quantity: 1,
po_status: "requested",
po_remark: "",
requested_by: "",
requested_date: new Date().toISOString().split("T")[0]
};
showAddPOModal = true;
}
// Save new purchase order
async function saveAddPO() {
// Basic required field check
if (
!addPOForm.po_type ||
!addPOForm.villa_id ||
!addPOForm.po_item ||
!addPOForm.requested_by ||
!addPOForm.po_quantity ||
addPOForm.po_quantity <= 0
) {
alert("Please fill in all required fields correctly.");
return;
}
// If you use auth:
const sessionId = await getSessionAuthId();
if (!sessionId) {
alert("You must be logged in.");
return;
}
const { error } = await supabase.from("vb_purchase_orders").insert({
po_type: addPOForm.po_type,
villa_id: addPOForm.villa_id,
po_item: addPOForm.po_item,
po_quantity: addPOForm.po_quantity,
po_status: "requested",
po_remark: addPOForm.po_remark,
requested_by: addPOForm.requested_by,
requested_date: addPOForm.requested_date,
created_at: new Date().toISOString()
});
if (error) {
console.error("Error adding PO:", error);
alert("Failed to add purchase order.");
return;
}
showAddPOModal = false;
await fetchPurchaseOrder();
}
// Fetch current user details
async function fetchCurrentUser() {
const {
data: { user }
} = await supabase.auth.getUser();
if (user) {
currentUserId = user.id;
const { data, error } = await supabase
.from("vb_users")
.select("full_name")
.eq("id", user.id)
.single();
if (!error && data) {
currentUserFullName = data.full_name;
}
}
}
// Save edited purchase order
async function saveEdit() {
const { error } = await supabase
.from("vb_purchase_orders")
.update({
prepared: editForm.prepared,
approved: editForm.approved,
acknowledged: editForm.acknowledged,
completed: editForm.completed,
received: editForm.received,
po_remark: editForm.po_remark,
updated_at: new Date().toISOString(), // ✅ safe current timestamp
updated_by: currentUserId // ✅ user UUID
})
.eq("id", selectedPO.id);
if (error) {
console.error("Error saving edit:", error);
alert("Failed to save changes.");
return;
}
showEditModal = false;
await fetchPurchaseOrder();
}
// save completed purchase order
async function saveCompleted() {
const { error } = await supabase
.from("vb_purchase_orders")
.update({
completed: completedForm.completed === "completed",
completed_by: completedForm.completed === "completed" ? currentUserId : null,
completed_date: completedForm.completed_date,
po_status: "completed"
})
.eq("id", selectedPO.id);
if (error) {
console.error("Error saving completed:", error);
alert("Failed to save.");
return;
}
// ✅ Fire the webhook after saving:
try {
await fetch("https://flow.catalis.app/webhook-test/vb_need_complete_new", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
po_id: selectedPO.id,
completed: completedForm.completed === "completed",
completed_by: completedForm.completed === "completed" ? currentUserId : null,
completed_date: completedForm.completed_date
})
});
} catch (webhookError) {
console.error("Webhook failed:", webhookError);
alert("Saved, but webhook failed.");
}
showCompletedModal = false;
await fetchPurchaseOrder();
}
// open prepared modal for purchase order
async function openPreparedModal(row) {
selectedPO = row;
// Prefill existing values
preparedForm = {
prepared_by: row.prepared_by || "",
prepared_date: row.prepared_date || "",
po_item: row.po_item || "",
po_quantity: row.po_quantity || 1,
q1_vendor: row.q1_vendor || "",
q2_vendor: row.q2_vendor || "",
q3_vendor: row.q3_vendor || "",
q1_vendor_price: row.q1_vendor_price || 0,
q2_vendor_price: row.q2_vendor_price || 0,
q3_vendor_price: row.q3_vendor_price || 0,
po_remark: row.po_remark || "",
approved_quantity: row.approved_quantity || 0,
approved_vendor: row.approved_vendor || "",
approved_price: row.approved_price || 0,
total_approved_order_amount: row.approved_quantity * row.approved_price
};
// Load dropdowns
await fetchPreparedDropdowns();
showPreparedModal = true;
}
// Fetch dropdowns for prepared modal
async function fetchPreparedDropdowns() {
const [{ data: employees }, { data: items }, { data: vendors }] = await Promise.all([
supabase.from("vb_employee").select("id, employee_name").eq("employee_status", "Active"),
supabase.from("vb_po_item").select("id, item_name"),
supabase.from("vb_vendor").select("id, name")
]);
preparedByOptions = employees || [];
poItemOptions = items || [];
vendorOptions = vendors || [];
}
// Save fetch purchase order
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("vb_purchaseorder_data")
.select("*")
.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;
}
if (!data || data.length === 0) {
console.warn("No purchase orders found.");
allRows = [];
return;
}
allRows = data.map((row: any) => {
return {
...row,
issue_name: row.issue_name || "N/A",
villa_name: row.villa_data || "N/A",
po_number: row.purchase_order_number || "",
approval: row.approval || "",
completed_status: row.completed_status || "",
proses_to_approval: row.proses_to_approval || false,
po_price: row.po_price || 0,
po_total_price: row.po_total_price || 0,
} as PurchaseOrderDisplay;
});
console.log("✅ Fetched rows:", data);
console.log("✅ Final mapped rows:", allRows);
}
//fetch all issues
async function fetchIssues() {
const { data, error } = await supabase
.from("vb_issues")
.select("id, name");
if (error) {
console.error("Error fetching issues:", error);
return [];
}
issues = data.map((issue) => ({
id: issue.id,
name: issue.name,
}));
}
// Fetch vendors for dropdown
async function fetchVendors() {
const { data, error } = await supabase
.from("vb_vendor")
.select("id, name");
if (error) {
console.error("Error fetching vendors:", error);
return [];
}
vendors = data.map((vendor) => ({
id: vendor.id,
name: vendor.name,
}));
}
// Open modal for adding or editing purchase order
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;
}
// Save prepared purchase order
async function savePrepared() {
const { error } = await supabase
.from("vb_purchase_orders")
.update({
prepared_by: preparedForm.prepared_by,
prepared_date: preparedForm.prepared_date,
po_item: preparedForm.po_item,
po_quantity: preparedForm.po_quantity,
q1_vendor: preparedForm.q1_vendor,
q2_vendor: preparedForm.q2_vendor,
q3_vendor: preparedForm.q3_vendor,
q1_vendor_price: preparedForm.q1_vendor_price,
q2_vendor_price: preparedForm.q2_vendor_price,
q3_vendor_price: preparedForm.q3_vendor_price,
approved_quantity: preparedForm.approved_quantity,
approved_vendor: preparedForm.approved_vendor,
approved_price: preparedForm.approved_price,
total_approved_order_amount: preparedForm.total_approved_order_amount,
po_remark: preparedForm.po_remark,
po_status: "prepared",
prepared: true,
})
.eq("id", selectedPO.id);
if (error) {
console.error("Error saving prepared data:", error);
alert("Failed to save.");
return;
}
try {
await fetch("https://flow.catalis.app/webhook/vb_approval_po_new", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
po_id: selectedPO.id,
po_status: "prepared",
prepared_by: preparedForm.prepared_by,
prepared_date: preparedForm.prepared_date,
po_item: preparedForm.po_item,
po_quantity: preparedForm.po_quantity,
approved_quantity: preparedForm.approved_quantity,
approved_vendor: preparedForm.approved_vendor,
approved_price: preparedForm.approved_price,
total_approved_order_amount: preparedForm.total_approved_order_amount,
}),
});
} catch (webhookError) {
console.error("Failed to fire webhook:", webhookError);
alert("Save worked but webhook failed.");
}
showPreparedModal = false;
await fetchPurchaseOrder();
}
// Save project function
async function saveProject() {
// session check
const session = await supabase.auth.getSession();
if (!session.data.session) {
alert("You must be logged in to perform this action.");
return;
}
const user = session.data.session.user;
const inputBy = user.id;
if (!validateInput()) {
return;
}
purchaseOrderInsert = {
issue_id: newPurchaseOrders.issue_id || "",
prepared_date: newPurchaseOrders.prepared_date || "",
po_type: newPurchaseOrders.po_type || "",
po_quantity: newPurchaseOrders.po_quantity || 0,
po_price: newPurchaseOrders.po_price || 0,
po_total_price:
newPurchaseOrders.po_quantity * newPurchaseOrders.po_price || 0,
input_by: inputBy,
updated_by: inputBy,
updated_at: new Date().toISOString(),
};
if (isEditing && currentEditingId) {
const { data, error } = await supabase
.from("vb_purchase_orders")
.update(purchaseOrderInsert)
.eq("id", currentEditingId);
if (error) {
console.error("Error updating purchase order:", error);
return;
}
} else {
const { data, error } = await supabase
.from("vb_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("vb_purchase_orders")
.delete()
.eq("id", id);
if (error) {
console.error("Error deleting project:", error);
return;
}
await fetchPurchaseOrder();
}
// Save received purchase order
async function saveReceived() {
const { error } = await supabase
.from("vb_purchase_orders")
.update({
received: receivedForm.received === "received",
received_by: receivedForm.received === "received" ? currentUserId : null,
received_date: receivedForm.received_date,
po_status: "received",
})
.eq("id", selectedPO.id);
if (error) {
console.error("Error saving received:", error);
alert("Failed to save.");
return;
}
// ✅ Fire the webhook after the DB update succeeds:
try {
await fetch("https://flow.catalis.app/webhook-test/vb_need_received_new", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
po_id: selectedPO.id,
received: receivedForm.received === "received",
received_by: receivedForm.received === "received" ? currentUserId : null,
received_date: receivedForm.received_date
})
});
} catch (webhookError) {
console.error("Webhook failed:", webhookError);
alert("Saved, but webhook failed.");
}
showReceivedModal = false;
await fetchPurchaseOrder();
}
// Save acknowledged purchase order
async function saveAcknowledged() {
const { error } = await supabase
.from("vb_purchase_orders")
.update({
acknowledged: acknowledgedForm.acknowledged === "acknowledged",
acknowledge_by: acknowledgedForm.acknowledged === "acknowledged" ? currentUserId : null,
acknowledged_date: acknowledgedForm.acknowledged_date,
po_status: "acknowledged"
})
.eq("id", selectedPO.id);
if (error) {
console.error("Error saving acknowledged:", error);
alert("Failed to save.");
return;
}
// ✅ Fire the webhook after successful update:
try {
await fetch("https://flow.catalis.app/webhook/vb_acknowledged_new", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
po_id: selectedPO.id,
acknowledged: acknowledgedForm.acknowledged === "acknowledged",
acknowledged_by: acknowledgedForm.acknowledged === "acknowledged" ? currentUserId : null,
acknowledged_date: acknowledgedForm.acknowledged_date
})
});
} catch (webhookError) {
console.error("Failed to fire webhook:", webhookError);
alert("Saved, but webhook failed.");
}
showAcknowledgedModal = false;
await fetchPurchaseOrder();
}
//fetch on mount
onMount(() => {
fetchPurchaseOrder();
fetchVendors();
fetchCurrentUser();
});
</script>
<div>
<div
class="p-6 bg-white shadow-md rounded-2xl mb-4 flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4"
>
<div>
<h2
class="text-lg font-semibold text-gray-800 flex items-center gap-2"
>
<span>📦</span>
Purchase Order List
</h2>
<p class="text-sm text-gray-600">
Manage your purchase orders efficiently. You can add, edit, or
delete purchase orders as needed.
</p>
</div>
<div class="flex flex-col sm:flex-row sm:items-center gap-2">
<input
type="text"
placeholder="🔍 Search by name..."
class="border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:outline-none px-4 py-2 rounded-xl text-sm w-64 transition"
on:input={(e) => {
const searchTerm = (
e.target as HTMLInputElement
).value.toLowerCase();
fetchPurchaseOrder(null, searchTerm, "created_at", "desc");
}}
/>
<select
class="border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:outline-none px-4 py-2 rounded-xl text-sm w-48 transition"
on:change={(e) => {
const filter = (e.target as HTMLSelectElement).value;
fetchPurchaseOrder(filter, null, null, "desc");
}}
>
<option value="">All Issues</option>
<option value="PROJECT">Project Issues</option>
<option value="PURCHASE_ORDER">Purchase Order Issues</option>
</select>
<button
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm"
on:click={openAddPOModal}
>
Add Purchase Order
</button>
</div>
</div>
<div class="overflow-x-auto rounded-lg shadow mb-4">
<table class="min-w-[1000px] divide-y divide-gray-200 text-sm w-max">
<thead class="bg-gray-100">
<tr>
{#each columns as col}
{#if col.key === "issue_name"}
<th
class="sticky left-0 px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
style="background-color: #f0f8ff; z-index: 10;"
>
{col.title}
</th>
{:else}
<th
class="px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
>
{col.title}
</th>
{/if}
{/each}
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each paginatedRows as row}
<tr class="hover:bg-gray-50 transition">
{#each columns as col}
{#if col.key === "requested_date" || col.key === "po_due" || col.key === "updated_at"}
<td class="px-4 py-2 text-gray-500">
{#if row[col.key]}
{new Date(row[col.key]).toLocaleString()}
{:else}
{/if}
</td>
{:else if col.key === "po_remark"}
<td class="px-4 py-2 text-gray-700 max-w-xs whitespace-normal align-top break-words">
{row[col.key] || "—"}
</td>
{:else if col.key === "issue_name"}
<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.issue_name || "—"}
</td>
{:else if col.key === "prepared"}
<td class="px-4 py-2 text-center">
<button
class="bg-blue-600 text-white text-xs px-3 py-1.5 rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
on:click={() => openPreparedModal(row)}
disabled={row.prepared === true }
>
{row.prepared === true ? "Prepared" : "Set Prepared"}
</button>
</td>
{:else if col.key === "approved"}
<td class="px-4 py-2 text-center">
<button
class="inline-flex items-center gap-1 rounded bg-yellow-600 px-3 py-1.5 text-white text-xs font-medium
hover:bg-yellow-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
on:click={() => openApprovalModal(row)}
disabled={
row.approved === true ||
row.approved === false ||
row.po_status !== 'prepared'
}
>
{#if row.approved === true}
Approved
{:else if row.approved === false}
Rejected
{:else if row.po_status !== 'prepared'}
Not Prepared
{:else}
Approval
{/if}
</button>
</td>
{:else if col.key === "acknowledged"}
<td class="px-4 py-2 text-center">
<button
class="inline-flex items-center gap-1 rounded bg-green-600 px-3 py-1.5 text-white text-xs font-medium
hover:bg-green-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
on:click={() => openAcknowledgedModal(row)}
disabled={
row.approved !== true || row.acknowledged === true || row.acknowledged === false
}
>
{#if row.acknowledged === true}
Acknowledged
{:else if row.acknowledged === false}
Rejected
{:else if row.approved !== true}
Pending
{:else}
Acknowledge
{/if}
</button>
</td>
{:else if col.key === "completed"}
<td class="px-4 py-2 text-center">
<button
class="inline-flex items-center gap-1 rounded bg-purple-600 px-3 py-1.5 text-white text-xs font-medium
hover:bg-purple-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
on:click={() => openCompletedModal(row)}
disabled={
row.acknowledged !== true || row.completed === true || row.completed === false
}
>
{#if row.completed === true}
Completed
{:else if row.completed === false}
Rejected
{:else if row.acknowledged !== true}
Pending
{:else}
Complete
{/if}
</button>
</td>
{:else if col.key === "received"}
<td class="px-4 py-2 text-center">
<button
class="inline-flex items-center gap-1 rounded bg-green-600 px-3 py-1.5 text-white text-xs font-medium
hover:bg-green-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
on:click={() => openReceivedModal(row)}
disabled={
row.completed !== true || row.received === true || row.received === false
}
>
{#if row.received === true}
Received
{:else if row.received === false}
Rejected
{:else if row.completed !== true}
Pending
{:else}
Receive
{/if}
</button>
</td>
{:else if col.key === "payment"}
<td class="px-4 py-2 text-center">
<button
class="inline-flex items-center gap-1 rounded bg-emerald-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-emerald-700"
on:click={() => openPaymentModal(row)}
>
Payment
</button>
</td>
{:else if col.key === "actions"}
<td class="px-4 py-2">
<button
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-blue-700"
on:click={() => openEditModal(row)}
>
✏️ Edit
</button>
<button
class="inline-flex items-center gap-1 rounded bg-red-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-red-700"
on:click={() => deleteProject(row.id)}
>
🗑️ Delete
</button>
</td>
{:else}
<td class="px-4 py-2 text-gray-700">
{row[col.key] ?? "—"}
</td>
{/if}
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
<!-- Pagination controls -->
<div class="flex justify-between items-center text-sm">
<div>
Showing {(currentPage - 1) * rowsPerPage + 1}
{Math.min(currentPage * rowsPerPage, allRows.length)} of {allRows.length}
</div>
<div class="space-x-2">
<button
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
on:click={() => goToPage(currentPage - 1)}
disabled={currentPage === 1}
>
Previous
</button>
{#each Array(totalPages)
.fill(0)
.map((_, i) => i + 1) as page}
<button
class="px-3 py-1 rounded border text-sm
{currentPage === page
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white border-gray-300 hover:bg-gray-100'}"
on:click={() => goToPage(page)}
>
{page}
</button>
{/each}
<button
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
on:click={() => goToPage(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</div>
</div>
{#if showModal}
<div
class="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-50 z-50"
>
<div
class="bg-white p-6 rounded shadow-lg w-[600px] max-h-[90vh] overflow-y-auto space-y-4"
>
<h3 class="text-lg font-semibold mb-4">
{isEditing ? "Edit Project" : "Add Project"}
</h3>
<form on:submit|preventDefault={saveProject}>
<!-- choose issuess -->
<div class="mb-4">
<label
for="issue_id"
class="block text-sm font-medium text-gray-700 mb-1"
>
Choose Issue
</label>
<select
id="issue_id"
bind:value={newPurchaseOrders.issue_id}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
on:change={(e: Event) => {
const selectedIssue =
(e.target as HTMLSelectElement)?.value ?? "";
newPurchaseOrders.issue_id = selectedIssue;
}}
disabled
>
<option value="">Select Issue</option>
{#each issues as issue}
<option value={issue.id}>{issue.name}</option>
{/each}
</select>
</div>
{#each formColumns as col}
{#if col.key === "po_status"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<select
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
on:change={(e: Event) => {
const selectedOption =
(e.target as HTMLSelectElement)
?.value ?? "";
const option =
getStatusOption(selectedOption);
if (
option?.value === "APPROVED" &&
!validateInput()
) {
e.preventDefault();
return;
}
}}
>
{#each statusOptions as option}
<option
value={option.value}
style="background-color: {option.color};"
>
{option.label}
</option>
{/each}
</select>
</div>
{:else if col.key === "po_type"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<select
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
>
<option value="">Select PO Type</option>
<option value="Regular">Regular</option>
<option value="Urgent">Urgent</option>
</select>
</div>
{:else if col.key === "prepared_date"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<input
type="date"
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
/>
</div>
{:else if col.key === "po_price"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<input
name="po_price"
placeholder="Enter PO Price"
type="number"
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
/>
</div>
{:else if col.key === "po_quantity"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<input
name="po_quantity"
placeholder="Enter PO Quantity"
type="number"
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
/>
</div>
{:else if col.key === "po_total_price"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<!-- calculate po quantity * price -->
<input
name="po_total_price"
type="number"
id={col.key}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
readonly
value={newPurchaseOrders.po_quantity *
newPurchaseOrders.po_price}
/>
</div>
{:else if col.key === "approved_price"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<input
type="number"
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
/>
</div>
{:else if col.key === "approved_vendor"}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<select
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
on:change={(e: Event) => {
const selectedVendor =
(e.target as HTMLSelectElement)
?.value ?? "";
newPurchaseOrders[col.key] = selectedVendor;
}}
>
<option value="">Select Vendor</option>
{#each vendors as vendor}
<option value={vendor.id}>
{vendor.name}
</option>
{/each}
</select>
</div>
{:else}
<div class="mb-4">
<label
for={col.key}
class="block text-sm font-medium text-gray-700 mb-1"
>
{col.title}
</label>
<input
type="text"
id={col.key}
bind:value={newPurchaseOrders[col.key]}
class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500"
/>
</div>
{/if}
{/each}
<div class="flex justify-end space-x-2">
<button
type="button"
class="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
on:click={() => (showModal = false)}
>
Cancel
</button>
<button
type="submit"
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Save
</button>
</div>
</form>
</div>
</div>
{/if}
{#if showPreparedModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-lg font-bold">Set Prepared</h2>
<!-- prepared_by -->
<label>Prepared By</label>
<select bind:value={preparedForm.prepared_by} class="w-full border p-2">
<option value="" disabled>Select Employee</option>
{#each preparedByOptions as emp}
<option value={emp.id}>{emp.employee_name}</option>
{/each}
</select>
<!-- prepared_date -->
<label>Prepared Date</label>
<input type="date" bind:value={preparedForm.prepared_date} class="w-full border p-2" />
<!-- po_item -->
<label>PO Item</label>
<select bind:value={preparedForm.po_item} class="w-full border p-2">
<option value="" disabled>Select Item</option>
{#each poItemOptions as item}
<option value={item.item_name}>{item.item_name}</option>
{/each}
</select>
<!-- po_quantity -->
<label>PO Quantity</label>
<input type="number" bind:value={preparedForm.po_quantity} class="w-full border p-2" />
<!-- Vendors and prices -->
<label>Q1 Vendor</label>
<select bind:value={preparedForm.q1_vendor} class="w-full border p-2">
<option value="" disabled>Select Vendor</option>
{#each vendorOptions as v}
<option value={v.name}>{v.name}</option>
{/each}
</select>
<input type="number" bind:value={preparedForm.q1_vendor_price} placeholder="Q1 Vendor Price" class="w-full border p-2" />
<!-- Repeat for Q2, Q3 -->
<!-- Approved -->
<label>Approved Quantity</label>
<input type="number" bind:value={preparedForm.approved_quantity} on:input={() => updateTotalAmount()} class="w-full border p-2" />
<label>Approved Vendor</label>
<select bind:value={preparedForm.approved_vendor} class="w-full border p-2">
<option value="" disabled>Select Vendor</option>
{#each vendorOptions as v}
<option value={v.name}>{v.name}</option>
{/each}
</select>
<label>Approved Price</label>
<input type="number" bind:value={preparedForm.approved_price} on:input={() => updateTotalAmount()} class="w-full border p-2" />
<label>Total Approved Order Amount</label>
<input type="number" value={preparedForm.total_approved_order_amount} disabled class="w-full border p-2 bg-gray-100" />
<label>PO Remark</label>
<textarea bind:value={preparedForm.po_remark} class="w-full border p-2"></textarea>
<div class="flex justify-end space-x-2 pt-4">
<button on:click={savePrepared} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showPreparedModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}
{#if showPaymentModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-xl font-bold">Set Payment</h2>
<!-- Payment Method -->
<label>Payment Method</label>
<select bind:value={paymentForm.payment_method} class="w-full border p-2">
<option value="" disabled>Select Method</option>
<option value="Gradualy">Gradualy</option>
<option value="Full Before Received">Full Before Received</option>
<option value="Full After Received">Full After Received</option>
</select>
<!-- Total Approved Order Amount (hidden input, readonly) -->
<label>Total Approved Order Amount</label>
<input type="number" value={paymentForm.total_approved_order_amount} disabled class="w-full border p-2 bg-gray-100"/>
<!-- Payment Amounts -->
{#each [1,2,3,4,5,6] as num}
<label>{ordinal(num)} Pay Amount</label>
<input
type="number"
bind:value={paymentForm[`${ordinal(num)}_pay_amt`]}
on:input={updateDueRemaining}
class="w-full border p-2"
/>
<label>{ordinal(num)} Pay Date</label>
<input
type="date"
bind:value={paymentForm[`${ordinal(num)}_pay_date`]}
class="w-full border p-2"
/>
{/each}
<!-- Due Remaining -->
<label>Due Remaining</label>
<input type="number" value={paymentForm.due_remaining} disabled class="w-full border p-2 bg-gray-100"/>
<!-- Buttons -->
<div class="flex justify-end space-x-2 pt-4">
<button on:click={savePayment} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showPaymentModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}
{#if showApprovalModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-md max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-xl font-bold">Approve Purchase Order</h2>
<!-- PO Number -->
<label>PO Number</label>
<input
type="text"
value={approvalForm.po_number}
disabled
class="w-full border p-2 bg-gray-100"
/>
<!-- Approval -->
<label>Approval</label>
<select bind:value={approvalForm.approval} class="w-full border p-2">
<option value="" disabled>Select Approval</option>
<option value="approve">Approve</option>
<option value="reject">Reject</option>
</select>
{#if approvalForm.approval === 'reject'}
<label>Reject Comment</label>
<textarea
bind:value={approvalForm.reject_comment}
class="w-full border p-2"
rows="3"
placeholder="Enter reason for rejection..."
></textarea>
{/if}
<!-- Hidden: approved_by -->
<input type="hidden" value={approvalForm.approved_by} />
<!-- Hidden: approved_date -->
<input type="hidden" value={approvalForm.approved_date} />
<!-- Actions -->
<div class="flex justify-end space-x-2 pt-4">
<button on:click={saveApproval} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showApprovalModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}
{#if showAcknowledgedModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-md max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-xl font-bold">Acknowledge Purchase Order</h2>
<!-- PO Number -->
<label>PO Number</label>
<input
type="text"
value={acknowledgedForm.po_number}
disabled
class="w-full border p-2 bg-gray-100"
/>
<!-- Acknowledged -->
<label>Acknowledged</label>
<select bind:value={acknowledgedForm.acknowledged} class="w-full border p-2">
<option value="" disabled>Select Acknowledgement</option>
<option value="acknowledged">Acknowledged</option>
<option value="reject">Reject</option>
</select>
<!-- Hidden fields -->
<input type="hidden" value={acknowledgedForm.acknowledged_by} />
<input type="hidden" value={acknowledgedForm.acknowledged_date} />
<!-- Actions -->
<div class="flex justify-end space-x-2 pt-4">
<button on:click={saveAcknowledged} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showAcknowledgedModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}
{#if showCompletedModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-md max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-xl font-bold">Complete Purchase Order</h2>
<!-- PO Number -->
<label>PO Number</label>
<input
type="text"
value={completedForm.po_number}
disabled
class="w-full border p-2 bg-gray-100"
/>
<!-- Completed -->
<label>Completion</label>
<select bind:value={completedForm.completed} class="w-full border p-2">
<option value="" disabled>Select Status</option>
<option value="completed">Completed</option>
<option value="reject">Reject</option>
</select>
<!-- Hidden -->
<input type="hidden" value={completedForm.completed_by} />
<input type="hidden" value={completedForm.completed_date} />
<!-- Actions -->
<div class="flex justify-end space-x-2 pt-4">
<button on:click={saveCompleted} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showCompletedModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}
{#if showReceivedModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-md max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-xl font-bold">Receive Purchase Order</h2>
<!-- PO Number -->
<label>PO Number</label>
<input
type="text"
value={receivedForm.po_number}
disabled
class="w-full border p-2 bg-gray-100"
/>
<!-- Received -->
<label>Received</label>
<select bind:value={receivedForm.received} class="w-full border p-2">
<option value="" disabled>Select Status</option>
<option value="received">Received</option>
<option value="reject">Reject</option>
</select>
<!-- Hidden -->
<input type="hidden" value={receivedForm.received_by} />
<input type="hidden" value={receivedForm.received_date} />
<!-- Actions -->
<div class="flex justify-end space-x-2 pt-4">
<button on:click={saveReceived} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showReceivedModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}
{#if showEditModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-xl font-bold">Edit Purchase Order</h2>
<!-- PO Number -->
<label>PO Number</label>
<input type="text" value={editForm.po_number} disabled class="w-full border p-2 bg-gray-100"/>
<!-- Issue Name -->
<label>Issue Name</label>
<input type="text" value={editForm.issue_name} disabled class="w-full border p-2 bg-gray-100"/>
<!-- Villa ID -->
<label>Villa ID</label>
<input type="text" value={editForm.villa_name} disabled class="w-full border p-2 bg-gray-100"/>
<!-- PO Status -->
<label>PO Status</label>
<input type="text" value={editForm.po_status} disabled class="w-full border p-2 bg-gray-100"/>
<!-- Status Toggles -->
<div class="space-y-2">
{#each ['prepared','approved','acknowledged','completed','received'] as key}
<div class="flex items-center gap-2">
<label class="w-32 capitalize">{key}</label>
<select bind:value={editForm[key]} class="w-40 border p-2">
<option value={null}>Unset</option>
<option value={true}>True</option>
<option value={false}>False</option>
</select>
<button
type="button"
class="text-xs text-blue-600 underline"
on:click={() => editForm[key] = null}
>Reset</button>
</div>
{/each}
</div>
<!-- PO Remark -->
<label>PO Remark</label>
<textarea
bind:value={editForm.po_remark}
class="w-full border p-2"
rows="3"
></textarea>
<!-- Actions -->
<div class="flex justify-end space-x-2 pt-4">
<button on:click={saveEdit} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showEditModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}
{#if showAddPOModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto p-6 space-y-4">
<h2 class="text-xl font-bold">Add Purchase Order</h2>
<!-- PO Type -->
<label>PO Type</label>
<select bind:value={addPOForm.po_type} class="w-full border p-2">
<option value="" disabled>Select PO Type</option>
<option value="purchase">Purchase</option>
<option value="project">Project</option>
<option value="repair">Repair</option>
</select>
<!-- Villa -->
<label>Villa</label>
<select bind:value={addPOForm.villa_id} class="w-full border p-2">
<option value="" disabled>Select Villa</option>
{#each villaOptions as v}
<option value={v.id}>{v.villa_name}</option>
{/each}
</select>
<!-- PO Item -->
<label>PO Item</label>
<select bind:value={addPOForm.po_item} class="w-full border p-2">
<option value="" disabled>Select Item</option>
{#each poItemOptions as item}
<option value={item.item_name}>{item.item_name}</option>
{/each}
</select>
<!-- Quantity -->
<label>PO Quantity</label>
<input type="number" bind:value={addPOForm.po_quantity} class="w-full border p-2"/>
<!-- Remark -->
<label>PO Remark</label>
<textarea bind:value={addPOForm.po_remark} class="w-full border p-2"></textarea>
<!-- Requested By -->
<label>Requested By</label>
<select bind:value={addPOForm.requested_by} class="w-full border p-2">
<option value="" disabled>Select Employee</option>
{#each employeeOptions as e}
<option value={e.id}>{e.employee_name}</option>
{/each}
</select>
<!-- Requested Date -->
<label>Requested Date</label>
<input type="date" bind:value={addPOForm.requested_date} class="w-full border p-2"/>
<!-- Actions -->
<div class="flex justify-end space-x-2 pt-4">
<button on:click={saveAddPO} class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
<button on:click={() => showAddPOModal = false} class="px-4 py-2 rounded border">Cancel</button>
</div>
</div>
</div>
</div>
{/if}