2693 lines
102 KiB
Svelte
2693 lines
102 KiB
Svelte
<script lang="ts">
|
||
import { onMount } from "svelte";
|
||
import { supabase } from "$lib/supabaseClient";
|
||
import { getSessionAuthId } from "$lib/utils/authUtil";
|
||
import CurrencyInput from "$lib/CurrencyInput.svelte";
|
||
|
||
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: "purchase_order_number", title: "PO Number" },
|
||
{ key: "issue_name", title: "Issue Name" },
|
||
{ key: "requested_date", title: "Requested Date" },
|
||
{ 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 sortColumn: string | null = "created_at"; // or any default
|
||
let sortOrder: "asc" | "desc" = "desc";
|
||
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: "",
|
||
po_remark: "",
|
||
approved_quantity: 0,
|
||
approved_vendor: "",
|
||
approved_price: 0,
|
||
po_item: "",
|
||
total_approved_order_amount: 0,
|
||
};
|
||
|
||
type VillaField = {
|
||
id: string;
|
||
villa_name: string;
|
||
};
|
||
|
||
let villaOptions: VillaField[] = [];
|
||
let poItemOptions = [];
|
||
let employeeOptions = [];
|
||
let allRows: PurchaseOrderDisplay[] = [];
|
||
let currentPage = 1;
|
||
let rowsPerPage = 10;
|
||
let totalItems = 0;
|
||
let totalPages = 0;
|
||
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: "",
|
||
reject_comment: "",
|
||
acknowledged: "",
|
||
acknowledged_by: "",
|
||
acknowledged_date: "",
|
||
po_remark: "",
|
||
approved_quantity: 0,
|
||
approved_vendor: "",
|
||
approved_price: 0,
|
||
po_item: "",
|
||
total_approved_order_amount: 0,
|
||
};
|
||
let selectedVillaId: string | null = null;
|
||
let searchTerm: string = "";
|
||
let showPreparedModal = false;
|
||
let selectedStatus: string | null = null;
|
||
let selectedPO: string | null = 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 formattedPrice = "";
|
||
let showReceivedModal = false;
|
||
let receivedForm = {
|
||
po_number: "",
|
||
received: "",
|
||
reject_comment: "",
|
||
received_by: "",
|
||
received_date: "",
|
||
approved_quantity: 0,
|
||
approved_vendor: "",
|
||
approved_price: 0,
|
||
po_item: "",
|
||
total_approved_order_amount: 0,
|
||
|
||
};
|
||
let showCompletedModal = false;
|
||
let completedForm = {
|
||
po_number: "",
|
||
completed: "",
|
||
reject_comment: "",
|
||
completed_by: "",
|
||
completed_date: "",
|
||
po_remark: "",
|
||
approved_quantity: 0,
|
||
approved_vendor: "",
|
||
approved_price: 0,
|
||
po_item: "",
|
||
total_approved_order_amount: 0,
|
||
};
|
||
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 }[] = [];
|
||
let printingId: string | null = null;
|
||
|
||
$: formattedPrice = preparedForm.q1_vendor_price.toLocaleString("id-ID", {
|
||
style: "currency",
|
||
currency: "IDR",
|
||
minimumFractionDigits: 0,
|
||
});
|
||
// function Sort
|
||
function toggleSort(column: string) {
|
||
if (sortColumn === column) {
|
||
sortOrder = sortOrder === "asc" ? "desc" : "asc";
|
||
} else {
|
||
sortColumn = column;
|
||
sortOrder = "asc";
|
||
}
|
||
fetchPurchaseOrder(selectedVillaId, searchTerm, sortColumn, sortOrder);
|
||
}
|
||
|
||
// Function to format numbers as ordinal (1st, 2nd, 3rd, etc.)
|
||
function ordinal(num: number) {
|
||
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;
|
||
}
|
||
function pageRange(
|
||
totalPages: number,
|
||
currentPage: number,
|
||
): (number | string)[] {
|
||
const range: (number | string)[] = [];
|
||
const maxDisplay = 5;
|
||
|
||
if (totalPages <= maxDisplay + 2) {
|
||
for (let i = 1; i <= totalPages; i++) range.push(i);
|
||
} else {
|
||
const start = Math.max(2, currentPage - 2);
|
||
const end = Math.min(totalPages - 1, currentPage + 2);
|
||
|
||
range.push(1);
|
||
if (start > 2) range.push("...");
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
range.push(i);
|
||
}
|
||
|
||
if (end < totalPages - 1) range.push("...");
|
||
range.push(totalPages);
|
||
}
|
||
|
||
return range;
|
||
}
|
||
|
||
function changePage(page: number) {
|
||
if (page < 1 || page > totalPages || page === currentPage) return;
|
||
currentPage = page;
|
||
fetchPurchaseOrder(
|
||
selectedVillaId,
|
||
searchTerm,
|
||
sortColumn,
|
||
sortOrder,
|
||
(currentPage - 1) * rowsPerPage,
|
||
rowsPerPage,
|
||
selectedStatus,
|
||
);
|
||
}
|
||
// 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 || "",
|
||
po_remark: row.po_remark || "",
|
||
approved_quantity: row.approved_quantity || 0,
|
||
approved_vendor: row.approved_vendor || "",
|
||
approved_price: row.approved_price || 0,
|
||
po_item: row.po_item || "",
|
||
total_approved_order_amount: row.total_approved_order_amount || 0,
|
||
};
|
||
|
||
showApprovalModal = true;
|
||
}
|
||
// acknowledge purchase order
|
||
function openAcknowledgedModal(row) {
|
||
|
||
console.log("Opening acknowledged modal with row:", row);
|
||
|
||
selectedPO = row;
|
||
|
||
acknowledgedForm = {
|
||
po_number: row.purchase_order_number || "",
|
||
reject_comment: row.reject_comment || "",
|
||
acknowledged: row.acknowledged ? "acknowledged" : "reject",
|
||
acknowledged_by: currentUserId,
|
||
acknowledged_date: new Date().toISOString().split("T")[0],
|
||
po_remark: row.po_remark || "",
|
||
approved_quantity: row.approved_quantity || 0,
|
||
approved_vendor: row.approved_vendor || "",
|
||
approved_price: row.approved_price || 0,
|
||
po_item: row.po_item || "",
|
||
total_approved_order_amount: row.total_approved_order_amount || 0,
|
||
};
|
||
|
||
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],
|
||
approved_quantity: row.approved_quantity || 0,
|
||
approved_vendor: row.approved_vendor || "",
|
||
approved_price: row.approved_price || 0,
|
||
po_item: row.po_item || "",
|
||
total_approved_order_amount: row.total_approved_order_amount || 0,
|
||
};
|
||
|
||
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],
|
||
po_remark: row.po_remark || "",
|
||
approved_quantity: row.approved_quantity || 0,
|
||
approved_vendor: row.approved_vendor || "",
|
||
approved_price: row.approved_price || 0,
|
||
po_item: row.po_item || "",
|
||
total_approved_order_amount: row.total_approved_order_amount || 0,
|
||
};
|
||
|
||
console.log("Opening completed modal with form:", completedForm);
|
||
|
||
|
||
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",
|
||
po_remark: approvalForm.po_remark || null,
|
||
})
|
||
.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 fetchDropdowns() {
|
||
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 fetchDropdowns();
|
||
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,
|
||
poStatus: string | null = null,
|
||
) {
|
||
let query = supabase
|
||
.from("vb_purchaseorder_data")
|
||
.select("*", { count: "exact" })
|
||
.order(sortColumn || "created_at", {
|
||
ascending: sortOrder === "asc",
|
||
})
|
||
.range(offset, offset + limit - 1);
|
||
|
||
if (filter) {
|
||
query = query.eq("villa_id", filter);
|
||
}
|
||
|
||
if (search) {
|
||
query = query.or(
|
||
`purchase_order_number.ilike.%${search}%,` +
|
||
`issue_name.ilike.%${search}%,` +
|
||
`po_item.ilike.%${search}%`,
|
||
);
|
||
}
|
||
if (poStatus) {
|
||
query = query.eq("po_status", poStatus);
|
||
}
|
||
|
||
const { data, count, error } = await query;
|
||
|
||
console.log("total count:", count);
|
||
|
||
|
||
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);
|
||
|
||
totalItems = count || 0;
|
||
|
||
console.log("✅ Total items:", totalItems);
|
||
|
||
}
|
||
|
||
//fetch on mount
|
||
onMount(() => {
|
||
fetchPurchaseOrder(
|
||
selectedVillaId,
|
||
searchTerm,
|
||
sortColumn,
|
||
sortOrder,
|
||
(currentPage - 1) * rowsPerPage,
|
||
rowsPerPage,
|
||
selectedStatus,
|
||
);
|
||
fetchVendors();
|
||
fetchCurrentUser();
|
||
fetchDropdowns();
|
||
});
|
||
|
||
$: totalPages = Math.ceil(totalItems / rowsPerPage);
|
||
$: currentPage = 1;
|
||
|
||
//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 confirmed = confirm(
|
||
"Are you sure you want to delete this purchase order? This action cannot be undone.",
|
||
);
|
||
if (!confirmed) {
|
||
return; // User chickened out — good.
|
||
}
|
||
|
||
const { error } = await supabase
|
||
.from("vb_purchase_orders")
|
||
.delete()
|
||
.eq("id", id);
|
||
|
||
if (error) {
|
||
console.error("Error deleting project:", error);
|
||
alert("Failed to delete project.");
|
||
return;
|
||
}
|
||
|
||
await fetchPurchaseOrder();
|
||
alert("Purchase order deleted successfully.");
|
||
}
|
||
// 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();
|
||
}
|
||
// Print purchase order
|
||
async function printPO(row) {
|
||
printingId = row.id; // set loading
|
||
try {
|
||
const response = await fetch(
|
||
"https://flow.catalis.app/webhook/vb_print_po_new",
|
||
{
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(row),
|
||
},
|
||
);
|
||
|
||
if (!response.ok) {
|
||
console.error(
|
||
"Failed to call print webhook:",
|
||
response.statusText,
|
||
);
|
||
alert("Failed to send print request.");
|
||
return;
|
||
}
|
||
|
||
alert("Print request sent successfully!");
|
||
} catch (error) {
|
||
console.error("Error sending print request:", error);
|
||
alert("An error occurred while sending the print request.");
|
||
} finally {
|
||
printingId = null; // unset loading
|
||
}
|
||
}
|
||
</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"
|
||
bind:value={searchTerm}
|
||
on:input={(e) => {
|
||
searchTerm = (
|
||
e.target as HTMLInputElement
|
||
).value.toLowerCase();
|
||
fetchPurchaseOrder(
|
||
selectedVillaId,
|
||
searchTerm,
|
||
sortColumn,
|
||
sortOrder,
|
||
currentPage - 1,
|
||
rowsPerPage,
|
||
selectedStatus,
|
||
);
|
||
}}
|
||
/>
|
||
|
||
<!-- 🏡 Villa Filter -->
|
||
<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"
|
||
bind:value={selectedVillaId}
|
||
on:change={() => {
|
||
fetchPurchaseOrder(
|
||
selectedVillaId,
|
||
searchTerm,
|
||
sortColumn,
|
||
sortOrder,
|
||
currentPage - 1,
|
||
rowsPerPage,
|
||
selectedStatus,
|
||
);
|
||
}}
|
||
>
|
||
<option value="">All Villas</option>
|
||
{#each villaOptions as villa}
|
||
<option value={villa.id}>{villa.villa_name}</option>
|
||
{/each}
|
||
</select>
|
||
|
||
<!-- 📦 PO Status Filter -->
|
||
<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"
|
||
bind:value={selectedStatus}
|
||
on:change={(e) => {
|
||
selectedStatus = (e.target as HTMLSelectElement).value;
|
||
fetchPurchaseOrder(
|
||
selectedVillaId,
|
||
searchTerm,
|
||
sortColumn,
|
||
sortOrder,
|
||
currentPage - 1,
|
||
rowsPerPage,
|
||
selectedStatus,
|
||
);
|
||
}}
|
||
>
|
||
<option value="">All Statuses</option>
|
||
<option value="requested">Requested</option>
|
||
<option value="prepared">Prepared</option>
|
||
<option value="approved">Approved</option>
|
||
<option value="acknowledged">Acknowledged</option>
|
||
</select>
|
||
|
||
<!-- ➕ Add Button -->
|
||
<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 max-h-[70vh]">
|
||
<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 === "purchase_order_number"}
|
||
<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;"
|
||
on:click={() => toggleSort(col.key)}
|
||
>
|
||
{col.title}
|
||
{#if sortColumn === col.key}
|
||
{sortOrder === "asc" ? " 🔼" : " 🔽"}
|
||
{/if}
|
||
</th>
|
||
{:else}
|
||
<th
|
||
class="px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
|
||
on:click={() => toggleSort(col.key)}
|
||
>
|
||
{col.title}
|
||
{#if sortColumn === col.key}
|
||
{sortOrder === "asc" ? " 🔼" : " 🔽"}
|
||
{/if}
|
||
</th>
|
||
{/if}
|
||
{/each}
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-200 bg-white">
|
||
{#each allRows 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 === "purchase_order_number"}
|
||
<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.purchase_order_number || "—"}
|
||
</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>
|
||
|
||
<button
|
||
class="inline-flex items-center gap-1 rounded bg-teal-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-teal-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||
on:click={() => printPO(row)}
|
||
disabled={printingId === row.id ||
|
||
row.acknowledged !== true}
|
||
>
|
||
{#if printingId === row.id}
|
||
🔄 Printing...
|
||
{:else if row.acknowledged !== true}
|
||
❌ Not Acknowledged
|
||
{:else}
|
||
🖨️ Print
|
||
{/if}
|
||
</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, totalItems)} of {totalItems} items
|
||
</div>
|
||
<div class="flex items-center space-x-4">
|
||
|
||
<div class="space-x-2">
|
||
<label for="rowsPerPage" class="text-sm">Rows per page:</label>
|
||
<select
|
||
id="rowsPerPage"
|
||
class="border border-gray-300 px-2 py-1 rounded text-sm"
|
||
bind:value={rowsPerPage}
|
||
on:change={() => {
|
||
currentPage = 1; // Reset to first page on change
|
||
fetchPurchaseOrder(
|
||
selectedVillaId,
|
||
searchTerm,
|
||
sortColumn,
|
||
sortOrder,
|
||
0,
|
||
rowsPerPage,
|
||
selectedStatus,
|
||
);
|
||
}}
|
||
>
|
||
{#each [10, 20, 50, 100] as option}
|
||
<option value={option}>{option}</option>
|
||
{/each}
|
||
</select>
|
||
<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 pageRange(totalPages, currentPage) as page}
|
||
{#if page === "..."}
|
||
<span class="px-2">...</span>
|
||
{:else}
|
||
<button
|
||
on:click={() => changePage(page as number)}
|
||
class="px-2 py-1 border rounded {page ===
|
||
currentPage
|
||
? 'bg-blue-600 text-white border-blue-600'
|
||
: 'bg-white border-gray-300 hover:bg-gray-100'}"
|
||
>
|
||
{page}
|
||
</button>
|
||
{/if}
|
||
{/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>
|
||
</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>
|
||
<CurrencyInput
|
||
bind:value={preparedForm.q1_vendor_price}
|
||
label="Q1 Vendor Price"
|
||
/>
|
||
<label>Q2 Vendor</label>
|
||
<select
|
||
bind:value={preparedForm.q2_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>
|
||
<CurrencyInput
|
||
bind:value={preparedForm.q2_vendor_price}
|
||
label="Q1 Vendor Price"
|
||
/>
|
||
<label>Q3 Vendor</label>
|
||
<select
|
||
bind:value={preparedForm.q3_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>
|
||
<CurrencyInput
|
||
bind:value={preparedForm.q3_vendor_price}
|
||
label="Q1 Vendor Price"
|
||
/>
|
||
<!-- 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>
|
||
<CurrencyInput
|
||
bind:value={preparedForm.approved_price}
|
||
label="Approved Price"
|
||
onInput={updateTotalAmount}
|
||
/>
|
||
|
||
<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>
|
||
<CurrencyInput
|
||
bind:value={paymentForm[`${ordinal(num)}_pay_amt`]}
|
||
onInput={updateDueRemaining}
|
||
className="w-full"
|
||
/>
|
||
|
||
<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>
|
||
<CurrencyInput
|
||
value={paymentForm.due_remaining}
|
||
disabled
|
||
className="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"
|
||
/>
|
||
|
||
<!-- PO Item -->
|
||
{#if approvalForm.po_item}
|
||
<label>PO Item</label>
|
||
<input
|
||
type="text"
|
||
value={approvalForm.po_item}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Quantity -->
|
||
{#if approvalForm.approved_quantity != null}
|
||
<label>Approved Quantity</label>
|
||
<input
|
||
type="number"
|
||
value={approvalForm.approved_quantity}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Price -->
|
||
{#if approvalForm.approved_price != null}
|
||
<label>Approved Price</label>
|
||
<CurrencyInput
|
||
value={approvalForm.approved_price}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Total Approved Order Amount -->
|
||
{#if approvalForm.total_approved_order_amount != null}
|
||
<label>Total Approved Order Amount</label>
|
||
<CurrencyInput
|
||
value={approvalForm.total_approved_order_amount}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Vendor -->
|
||
{#if approvalForm.approved_vendor}
|
||
<label>Approved Vendor</label>
|
||
<input
|
||
type="text"
|
||
value={approvalForm.approved_vendor}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approval Decision -->
|
||
<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>
|
||
|
||
<!-- Reject Comment -->
|
||
{#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}
|
||
|
||
<!-- PO Remark (editable!) -->
|
||
<label>PO Remark</label>
|
||
<textarea
|
||
bind:value={approvalForm.po_remark}
|
||
class="w-full border p-2"
|
||
rows="3"
|
||
placeholder="Add or edit PO remark..."
|
||
></textarea>
|
||
|
||
<!-- Hidden fields -->
|
||
<input type="hidden" value={approvalForm.approved_by} />
|
||
<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>
|
||
<!-- Reject Comment -->
|
||
{#if acknowledgedForm.acknowledged === "reject"}
|
||
<label>Reject Comment</label>
|
||
<textarea
|
||
bind:value={acknowledgedForm.reject_comment}
|
||
class="w-full border p-2"
|
||
rows="3"
|
||
placeholder="Enter reason for rejection..."
|
||
></textarea>
|
||
{/if}
|
||
|
||
<!-- PO Item -->
|
||
{#if approvalForm.po_item}
|
||
<label>PO Item</label>
|
||
<input
|
||
type="text"
|
||
value={acknowledgedForm.po_item}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Quantity -->
|
||
{#if approvalForm.approved_quantity != null}
|
||
<label>Approved Quantity</label>
|
||
<input
|
||
type="number"
|
||
value={acknowledgedForm.approved_quantity}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Price -->
|
||
{#if approvalForm.approved_price != null}
|
||
<label>Approved Price</label>
|
||
<CurrencyInput
|
||
value={acknowledgedForm.approved_price}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Total Approved Order Amount -->
|
||
{#if approvalForm.total_approved_order_amount != null}
|
||
<label>Total Approved Order Amount</label>
|
||
<CurrencyInput
|
||
value={acknowledgedForm.total_approved_order_amount}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Vendor -->
|
||
{#if approvalForm.approved_vendor}
|
||
<label>Approved Vendor</label>
|
||
<input
|
||
type="text"
|
||
value={acknowledgedForm.approved_vendor}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Reject Comment -->
|
||
{#if completedForm.completed === "reject"}
|
||
<label>Reject Comment</label>
|
||
<textarea
|
||
bind:value={completedForm.reject_comment}
|
||
class="w-full border p-2"
|
||
rows="3"
|
||
placeholder="Enter reason for rejection..."
|
||
></textarea>
|
||
{/if}
|
||
|
||
<!-- PO Item -->
|
||
{#if completedForm.po_item}
|
||
<label>PO Item</label>
|
||
<input
|
||
type="text"
|
||
value={completedForm.po_item}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Quantity -->
|
||
{#if completedForm.approved_quantity != null}
|
||
<label>Approved Quantity</label>
|
||
<input
|
||
type="number"
|
||
value={completedForm.approved_quantity}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Price -->
|
||
{#if completedForm.approved_price != null}
|
||
<label>Approved Price</label>
|
||
<CurrencyInput
|
||
value={completedForm.approved_price}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Total Approved Order Amount -->
|
||
{#if completedForm.total_approved_order_amount != null}
|
||
<label>Total Approved Order Amount</label>
|
||
<CurrencyInput
|
||
value={completedForm.total_approved_order_amount}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Vendor -->
|
||
{#if completedForm.approved_vendor}
|
||
<label>Approved Vendor</label>
|
||
<input
|
||
type="text"
|
||
value={completedForm.approved_vendor}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- 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>
|
||
|
||
<!-- PO Item -->
|
||
{#if receivedForm.po_item}
|
||
<label>PO Item</label>
|
||
<input
|
||
type="text"
|
||
value={receivedForm.po_item}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Quantity -->
|
||
{#if receivedForm.approved_quantity != null}
|
||
<label>Approved Quantity</label>
|
||
<input
|
||
type="number"
|
||
value={receivedForm.approved_quantity}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Price -->
|
||
{#if receivedForm.approved_price != null}
|
||
<label>Approved Price</label>
|
||
<CurrencyInput
|
||
value={receivedForm.approved_price}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Total Approved Order Amount -->
|
||
{#if receivedForm.total_approved_order_amount != null}
|
||
<label>Total Approved Order Amount</label>
|
||
<CurrencyInput
|
||
value={receivedForm.total_approved_order_amount}
|
||
disabled
|
||
className="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- Approved Vendor -->
|
||
{#if receivedForm.approved_vendor}
|
||
<label>Approved Vendor</label>
|
||
<input
|
||
type="text"
|
||
value={receivedForm.approved_vendor}
|
||
disabled
|
||
class="w-full border p-2 bg-gray-100"
|
||
/>
|
||
{/if}
|
||
|
||
<!-- 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}
|