perbaikan data
This commit is contained in:
10
src/lib/utils/authUtil.ts
Normal file
10
src/lib/utils/authUtil.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { supabase } from "$lib/supabaseClient";
|
||||||
|
|
||||||
|
export async function getSessionAuthId() {
|
||||||
|
const { data: { session }, error } = await supabase.auth.getSession();
|
||||||
|
if (error) {
|
||||||
|
console.error("Error fetching session:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return session?.user?.id || null;
|
||||||
|
}
|
||||||
@@ -211,7 +211,6 @@
|
|||||||
{ key: "issue_number", title: "Issue Number" },
|
{ key: "issue_number", title: "Issue Number" },
|
||||||
{ key: "move_issue", title: "Move Issue" },
|
{ key: "move_issue", title: "Move Issue" },
|
||||||
{ key: "description_of_the_issue", title: "Description of The Issue" },
|
{ key: "description_of_the_issue", title: "Description of The Issue" },
|
||||||
{ key: "reported_date", title: "Reported Date" },
|
|
||||||
{ key: "issue_related_image", title: "Issue Related Image" },
|
{ key: "issue_related_image", title: "Issue Related Image" },
|
||||||
{ key: "issue_source", title: "Issue Source" },
|
{ key: "issue_source", title: "Issue Source" },
|
||||||
{ key: "reported_name", title: "Reported By" },
|
{ key: "reported_name", title: "Reported By" },
|
||||||
@@ -732,7 +731,7 @@
|
|||||||
row[col.key as keyof Issue] as
|
row[col.key as keyof Issue] as
|
||||||
| string
|
| string
|
||||||
| number,
|
| number,
|
||||||
).toLocaleDateString("en-US")
|
).toLocaleString("en-US")
|
||||||
: ""}
|
: ""}
|
||||||
</td>
|
</td>
|
||||||
{:else if col.key === "need_approval"}
|
{:else if col.key === "need_approval"}
|
||||||
@@ -751,6 +750,23 @@
|
|||||||
❌
|
❌
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
|
{:else if col.key === "created_at"}
|
||||||
|
<!-- beri jam jg -->
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{new Date(
|
||||||
|
row[col.key as keyof Issue] as string,
|
||||||
|
).toLocaleString("en-US")}
|
||||||
|
</td>
|
||||||
|
{:else if col.key === "updated_at"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{row[col.key as keyof Issue]
|
||||||
|
? new Date(
|
||||||
|
row[
|
||||||
|
col.key as keyof Issue
|
||||||
|
] as string,
|
||||||
|
).toLocaleString("en-US")
|
||||||
|
: ""}
|
||||||
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
<td class="px-4 py-2 text-gray-700"
|
<td class="px-4 py-2 text-gray-700"
|
||||||
>{row[col.key as keyof Issue]}</td
|
>{row[col.key as keyof Issue]}</td
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
villa_data?: string; // Optional, if not always present
|
villa_data?: string; // Optional, if not always present
|
||||||
};
|
};
|
||||||
|
|
||||||
type insetProject = {
|
type insertProject = {
|
||||||
issue_id: string;
|
issue_id: string;
|
||||||
input_by: string;
|
input_by: string;
|
||||||
project_due_date: string;
|
project_due_date: string;
|
||||||
picture_link: string;
|
picture_link: string;
|
||||||
|
updated_at?: string; // Optional, if not always present
|
||||||
|
updated_by?: string; // Optional, if not always present
|
||||||
};
|
};
|
||||||
|
|
||||||
type Projects = {
|
type Projects = {
|
||||||
@@ -34,6 +36,9 @@
|
|||||||
issue_id: string;
|
issue_id: string;
|
||||||
report_date: string;
|
report_date: string;
|
||||||
project_due_date: string;
|
project_due_date: string;
|
||||||
|
project_number?: string; // Optional, if not always present
|
||||||
|
updated_at?: string; // Optional, if not always present
|
||||||
|
updated_name?: string; // Optional, if not always present
|
||||||
};
|
};
|
||||||
|
|
||||||
let allRows: Projects[] = [];
|
let allRows: Projects[] = [];
|
||||||
@@ -51,11 +56,14 @@
|
|||||||
{ key: "picture_link", title: "Picture Link" },
|
{ key: "picture_link", title: "Picture Link" },
|
||||||
{ key: "need_approval", title: "Need Approval" },
|
{ key: "need_approval", title: "Need Approval" },
|
||||||
{ key: "area_of_villa", title: "Area of Villa" },
|
{ key: "area_of_villa", title: "Area of Villa" },
|
||||||
{ key: "input_by", title: "Input By" },
|
{ key: "input_name", title: "Input By" },
|
||||||
{ key: "issue_number", title: "Issue Number" },
|
{ key: "issue_number", title: "Issue Number" },
|
||||||
{ key: "villa_name", title: "Villa Name" },
|
{ key: "villa_name", title: "Villa Name" },
|
||||||
{ key: "report_date", title: "Report Date" },
|
{ key: "report_date", title: "Report Date" },
|
||||||
{ key: "project_due_date", title: "Project Due Date" },
|
{ key: "project_due_date", title: "Project Due Date" },
|
||||||
|
{ key: "project_number", title: "Project Number" },
|
||||||
|
{ key: "updated_at", title: "Updated At" },
|
||||||
|
{ key: "updated_name", title: "Updated By" },
|
||||||
{ key: "actions", title: "Actions" },
|
{ key: "actions", title: "Actions" },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -187,6 +195,10 @@
|
|||||||
"actions",
|
"actions",
|
||||||
"add_to_po",
|
"add_to_po",
|
||||||
"issue_number",
|
"issue_number",
|
||||||
|
"updated_at",
|
||||||
|
"updated_name",
|
||||||
|
"input_name",
|
||||||
|
"input_by",
|
||||||
];
|
];
|
||||||
const formColumns = columns.filter(
|
const formColumns = columns.filter(
|
||||||
(col) => !excludedKeys.includes(col.key),
|
(col) => !excludedKeys.includes(col.key),
|
||||||
@@ -207,7 +219,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//validation project make
|
//validation project make
|
||||||
function validateProjectCheckBox(project: insetProject): boolean {
|
function validateProjectCheckBox(project: insertProject): boolean {
|
||||||
if (!project.project_due_date) {
|
if (!project.project_due_date) {
|
||||||
console.error("Project due date is required");
|
console.error("Project due date is required");
|
||||||
alert("Project due date is required");
|
alert("Project due date is required");
|
||||||
@@ -259,6 +271,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function addToPo(project: Project) {
|
async function addToPo(project: Project) {
|
||||||
|
// session user
|
||||||
|
const session = await supabase.auth.getSession();
|
||||||
|
const user = session?.data?.session?.user;
|
||||||
|
if (!user) {
|
||||||
|
console.error("User not authenticated");
|
||||||
|
alert("User not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputBy = user.id; // Use user id as input_by
|
||||||
|
|
||||||
if (!project.add_to_po) {
|
if (!project.add_to_po) {
|
||||||
console.error("Project must be added to PO");
|
console.error("Project must be added to PO");
|
||||||
alert("Project must be added to PO");
|
alert("Project must be added to PO");
|
||||||
@@ -274,6 +297,8 @@
|
|||||||
input_by: project?.input_by,
|
input_by: project?.input_by,
|
||||||
project_due_date: project?.project_due_date,
|
project_due_date: project?.project_due_date,
|
||||||
picture_link: project?.picture_link,
|
picture_link: project?.picture_link,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
updated_by: inputBy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
@@ -310,6 +335,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveProject(event: Event) {
|
async function saveProject(event: Event) {
|
||||||
|
//get session user
|
||||||
|
const session = await supabase.auth.getSession();
|
||||||
|
const user = session?.data?.session?.user;
|
||||||
|
if (!user) {
|
||||||
|
console.error("User not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputBy = user.id; // Use email or id as input_by
|
||||||
|
|
||||||
const formData = new FormData(event.target as HTMLFormElement);
|
const formData = new FormData(event.target as HTMLFormElement);
|
||||||
|
|
||||||
// Upload image if selected
|
// Upload image if selected
|
||||||
@@ -326,12 +361,14 @@
|
|||||||
imagePreviewUrl = data?.path;
|
imagePreviewUrl = data?.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectUpdate: insetProject = {
|
const projectUpdate: insertProject = {
|
||||||
issue_id: formData.get("issue_id") as string,
|
issue_id: formData.get("issue_id") as string,
|
||||||
input_by: formData.get("input_by") as string,
|
input_by: inputBy,
|
||||||
project_due_date: formData.get("project_due_date") as string,
|
project_due_date: formData.get("project_due_date") as string,
|
||||||
picture_link:
|
picture_link:
|
||||||
imagePreviewUrl || (formData.get("picture_link") as string),
|
imagePreviewUrl || (formData.get("picture_link") as string),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
updated_by: user.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate project before saving
|
// Validate project before saving
|
||||||
@@ -553,6 +590,42 @@
|
|||||||
No Picture
|
No Picture
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
|
{:else if col.key === "project_due_date"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{#if row.project_due_date}
|
||||||
|
{new Date(
|
||||||
|
row.project_due_date,
|
||||||
|
).toLocaleString("en-US")}
|
||||||
|
{:else}
|
||||||
|
No Due Date
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{:else if col.key === "updated_at"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{#if row.updated_at}
|
||||||
|
{new Date(
|
||||||
|
row.updated_at,
|
||||||
|
).toLocaleString("en-US")}
|
||||||
|
{:else}
|
||||||
|
No Update
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{:else if col.key === "input_by"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{#if row.input_by}
|
||||||
|
{row.input_by}
|
||||||
|
{:else}
|
||||||
|
Not Yet Input
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{:else if col.key === "updated_name"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{#if row.updated_name}
|
||||||
|
{row.updated_name}
|
||||||
|
{:else}
|
||||||
|
No Updater
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
<td class="px-4 py-2 text-gray-700"
|
<td class="px-4 py-2 text-gray-700"
|
||||||
>{row[col.key as keyof Projects]}</td
|
>{row[col.key as keyof Projects]}</td
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Select from "svelte-select";
|
import Select from "svelte-select";
|
||||||
import { supabase } from "$lib/supabaseClient";
|
import { supabase } from "$lib/supabaseClient";
|
||||||
import { timestampToDateTime } from "$lib/utils/conversion";
|
import { timestampToDateTime } from "$lib/utils/conversion";
|
||||||
|
import { getSessionAuthId } from "$lib/utils/authUtil";
|
||||||
|
|
||||||
type PurchaseOrderInsert = {
|
type PurchaseOrderInsert = {
|
||||||
issue_id: string;
|
issue_id: string;
|
||||||
@@ -11,6 +12,9 @@
|
|||||||
po_quantity: number;
|
po_quantity: number;
|
||||||
po_price: number;
|
po_price: number;
|
||||||
po_total_price: number;
|
po_total_price: number;
|
||||||
|
input_by: string;
|
||||||
|
updated_by: string;
|
||||||
|
updated_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let purchaseOrderInsert: PurchaseOrderInsert = {
|
let purchaseOrderInsert: PurchaseOrderInsert = {
|
||||||
@@ -20,6 +24,9 @@
|
|||||||
po_quantity: 0,
|
po_quantity: 0,
|
||||||
po_price: 0,
|
po_price: 0,
|
||||||
po_total_price: 0,
|
po_total_price: 0,
|
||||||
|
input_by: "",
|
||||||
|
updated_by: "",
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseOrders = {
|
type PurchaseOrders = {
|
||||||
@@ -72,6 +79,11 @@
|
|||||||
received: boolean;
|
received: boolean;
|
||||||
received_by: string;
|
received_by: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
updated_at?: string;
|
||||||
|
updated_by?: string;
|
||||||
|
updated_name?: string;
|
||||||
|
input_by?: string;
|
||||||
|
input_name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let allRows: PurchaseOrderDisplay[] = [];
|
let allRows: PurchaseOrderDisplay[] = [];
|
||||||
@@ -95,8 +107,8 @@
|
|||||||
{ key: "proses_to_approval", title: "PROSES TO APPROVAL" },
|
{ key: "proses_to_approval", title: "PROSES TO APPROVAL" },
|
||||||
{ key: "approved_vendor", title: "Approved Vendor" },
|
{ key: "approved_vendor", title: "Approved Vendor" },
|
||||||
{ key: "acknowledged", title: "Acknowledged" },
|
{ key: "acknowledged", title: "Acknowledged" },
|
||||||
{ key: "acknowledge_by", title: "Acknowledged By" },
|
{ key: "acknowledged_name", title: "Acknowledged By" },
|
||||||
{ key: "approved_by", title: "Approved By" },
|
{ key: "approved_name", title: "Approved By" },
|
||||||
{ key: "approved_price", title: "Approved Price" },
|
{ key: "approved_price", title: "Approved Price" },
|
||||||
{ key: "approved_quantity", title: "Approved Quantity" },
|
{ key: "approved_quantity", title: "Approved Quantity" },
|
||||||
{
|
{
|
||||||
@@ -106,7 +118,10 @@
|
|||||||
{ key: "approval", title: "Approval" },
|
{ key: "approval", title: "Approval" },
|
||||||
{ key: "completed_status", title: "Completed Status" },
|
{ key: "completed_status", title: "Completed Status" },
|
||||||
{ key: "received", title: "Received" },
|
{ key: "received", title: "Received" },
|
||||||
{ key: "received_by", title: "Received By" },
|
{ key: "received_name", title: "Received By" },
|
||||||
|
{ key: "inputed_name", title: "Input By" },
|
||||||
|
{ key: "updated_name", title: "Updated By" },
|
||||||
|
{ key: "updated_at", title: "Updated At" },
|
||||||
{ key: "created_at", title: "Created At" },
|
{ key: "created_at", title: "Created At" },
|
||||||
{ key: "actions", title: "Actions" }, // For edit/delete buttons
|
{ key: "actions", title: "Actions" }, // For edit/delete buttons
|
||||||
];
|
];
|
||||||
@@ -265,6 +280,10 @@
|
|||||||
"total_approved_order_amount",
|
"total_approved_order_amount",
|
||||||
"approved_vendor",
|
"approved_vendor",
|
||||||
"approved_price",
|
"approved_price",
|
||||||
|
"updated_at",
|
||||||
|
"updated_by",
|
||||||
|
"updated_name",
|
||||||
|
"inputed_name",
|
||||||
];
|
];
|
||||||
const formColumns = columns.filter(
|
const formColumns = columns.filter(
|
||||||
(col) => !excludedKeys.includes(col.key),
|
(col) => !excludedKeys.includes(col.key),
|
||||||
@@ -285,6 +304,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveProject() {
|
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()) {
|
if (!validateInput()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -297,6 +328,9 @@
|
|||||||
po_price: newPurchaseOrders.po_price || 0,
|
po_price: newPurchaseOrders.po_price || 0,
|
||||||
po_total_price:
|
po_total_price:
|
||||||
newPurchaseOrders.po_quantity * newPurchaseOrders.po_price || 0,
|
newPurchaseOrders.po_quantity * newPurchaseOrders.po_price || 0,
|
||||||
|
input_by: inputBy,
|
||||||
|
updated_by: inputBy,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isEditing && currentEditingId) {
|
if (isEditing && currentEditingId) {
|
||||||
@@ -400,13 +434,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateProsesToApproval(id: string, status: boolean) {
|
async function updateProsesToApproval(id: string, status: boolean) {
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateInputProsesToAprroval()) {
|
if (!validateInputProsesToAprroval()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ proses_to_approval: status, po_status: "REQUESTED" })
|
.update({
|
||||||
|
proses_to_approval: status,
|
||||||
|
po_status: "REQUESTED",
|
||||||
|
updated_by: sessionId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -425,6 +470,11 @@
|
|||||||
id: string,
|
id: string,
|
||||||
row: PurchaseOrderDisplay,
|
row: PurchaseOrderDisplay,
|
||||||
) {
|
) {
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const selectedOption = (e.target as HTMLSelectElement)?.value ?? "";
|
const selectedOption = (e.target as HTMLSelectElement)?.value ?? "";
|
||||||
const option = getStatusOption(selectedOption);
|
const option = getStatusOption(selectedOption);
|
||||||
|
|
||||||
@@ -454,9 +504,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function acknowledgedOk(id: string, status: boolean) {
|
async function acknowledgedOk(id: string, status: boolean) {
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ acknowledged: status })
|
.update({
|
||||||
|
acknowledged: status,
|
||||||
|
updated_by: sessionId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -468,9 +528,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function receivedOk(id: string, status: boolean) {
|
async function receivedOk(id: string, status: boolean) {
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ receivedOk: status })
|
.update({
|
||||||
|
receivedOk: status,
|
||||||
|
updated_by: sessionId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -486,6 +556,11 @@
|
|||||||
id: string,
|
id: string,
|
||||||
row: PurchaseOrderDisplay,
|
row: PurchaseOrderDisplay,
|
||||||
) {
|
) {
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const selectedOption = (e.target as HTMLSelectElement)?.value ?? "";
|
const selectedOption = (e.target as HTMLSelectElement)?.value ?? "";
|
||||||
const option = getStatusOption(selectedOption);
|
const option = getStatusOption(selectedOption);
|
||||||
|
|
||||||
@@ -503,7 +578,11 @@
|
|||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ approval: newPurchaseOrders.approval })
|
.update({
|
||||||
|
approval: newPurchaseOrders.approval,
|
||||||
|
updated_by: sessionId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -515,9 +594,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function completedStatusOk(id: string, status: string) {
|
async function completedStatusOk(id: string, status: string) {
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ completed_status: status })
|
.update({
|
||||||
|
completed_status: status,
|
||||||
|
updated_by: sessionId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -753,6 +841,14 @@
|
|||||||
>
|
>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
{:else if col.key === "updated_at"}
|
||||||
|
<td class="px-4 py-2 text-gray-500"
|
||||||
|
>{new Date(
|
||||||
|
row[
|
||||||
|
col.key as keyof PurchaseOrderDisplay
|
||||||
|
] as string,
|
||||||
|
).toLocaleString()}</td
|
||||||
|
>
|
||||||
{:else if col.key === "actions"}
|
{:else if col.key === "actions"}
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
<button
|
<button
|
||||||
@@ -770,11 +866,11 @@
|
|||||||
</td>
|
</td>
|
||||||
{:else if col.key === "created_at"}
|
{:else if col.key === "created_at"}
|
||||||
<td class="px-4 py-2 text-gray-500"
|
<td class="px-4 py-2 text-gray-500"
|
||||||
>{timestampToDateTime(
|
>{new Date(
|
||||||
row[
|
row[
|
||||||
col.key as keyof PurchaseOrderDisplay
|
col.key as keyof PurchaseOrderDisplay
|
||||||
] as string,
|
] as string,
|
||||||
)}</td
|
).toLocaleString()}</td
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<td class="px-4 py-2 text-gray-700"
|
<td class="px-4 py-2 text-gray-700"
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
approved_by: string;
|
approved_by: string;
|
||||||
approved_price: number;
|
approved_price: number;
|
||||||
completed_status: string;
|
completed_status: string;
|
||||||
|
updated_by?: string;
|
||||||
|
updated_at?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let purchaseOrderInsert: PurchaseOrderInsert = {
|
let purchaseOrderInsert: PurchaseOrderInsert = {
|
||||||
@@ -27,6 +29,8 @@
|
|||||||
approved_by: "",
|
approved_by: "",
|
||||||
approved_price: 0,
|
approved_price: 0,
|
||||||
completed_status: "",
|
completed_status: "",
|
||||||
|
updated_by: "",
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseOrders = {
|
type PurchaseOrders = {
|
||||||
@@ -50,6 +54,8 @@
|
|||||||
input_by: string;
|
input_by: string;
|
||||||
issue_id: string;
|
issue_id: string;
|
||||||
approved_by: string;
|
approved_by: string;
|
||||||
|
updated_at: string;
|
||||||
|
updated_by: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,6 +80,9 @@
|
|||||||
completed_status: string;
|
completed_status: string;
|
||||||
received: boolean;
|
received: boolean;
|
||||||
received_by: string;
|
received_by: string;
|
||||||
|
updated_name: string;
|
||||||
|
updated_at: string;
|
||||||
|
created_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let allRows: PurchaseOrderDisplay[] = [];
|
let allRows: PurchaseOrderDisplay[] = [];
|
||||||
@@ -96,7 +105,8 @@
|
|||||||
title: "Total Approved Order Amount",
|
title: "Total Approved Order Amount",
|
||||||
},
|
},
|
||||||
{ key: "acknowledged", title: "Acknowledged" },
|
{ key: "acknowledged", title: "Acknowledged" },
|
||||||
{ key: "acknowledge_by", title: "Acknowledge By" },
|
{ key: "updated_name", title: "Updated By" },
|
||||||
|
{ key: "updated_at", title: "Updated At" },
|
||||||
{ key: "created_at", title: "Created At" },
|
{ key: "created_at", title: "Created At" },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -249,6 +259,8 @@
|
|||||||
"approved_by",
|
"approved_by",
|
||||||
"name",
|
"name",
|
||||||
"po_status",
|
"po_status",
|
||||||
|
"updated_name",
|
||||||
|
"updated_at",
|
||||||
];
|
];
|
||||||
const formColumns = columns.filter(
|
const formColumns = columns.filter(
|
||||||
(col) => !excludedKeys.includes(col.key),
|
(col) => !excludedKeys.includes(col.key),
|
||||||
@@ -269,6 +281,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveProject() {
|
async function saveProject() {
|
||||||
|
const sessionId = await supabase.auth.getSession();
|
||||||
|
if (!sessionId) {
|
||||||
|
console.error("User not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
purchaseOrderInsert = {
|
purchaseOrderInsert = {
|
||||||
issue_id: newPurchaseOrders.issue_id || "",
|
issue_id: newPurchaseOrders.issue_id || "",
|
||||||
prepared_date: newPurchaseOrders.prepared_date || "",
|
prepared_date: newPurchaseOrders.prepared_date || "",
|
||||||
@@ -280,6 +298,8 @@
|
|||||||
approved_price: newPurchaseOrders.approved_price || "",
|
approved_price: newPurchaseOrders.approved_price || "",
|
||||||
approved_by: newPurchaseOrders.approved_by || "",
|
approved_by: newPurchaseOrders.approved_by || "",
|
||||||
completed_status: newPurchaseOrders.completed_status || "",
|
completed_status: newPurchaseOrders.completed_status || "",
|
||||||
|
updated_by: sessionId.data.session?.user.id || "",
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isEditing && currentEditingId) {
|
if (isEditing && currentEditingId) {
|
||||||
@@ -405,9 +425,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function acknowledgedOk(id: string, status: boolean) {
|
async function acknowledgedOk(id: string, status: boolean) {
|
||||||
|
const sessionId = await supabase.auth.getSession();
|
||||||
|
if (!sessionId) {
|
||||||
|
console.error("User not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ acknowledged: status, po_status: "ACKNOWLEDGED" })
|
.update({
|
||||||
|
acknowledged: status,
|
||||||
|
po_status: "ACKNOWLEDGED",
|
||||||
|
acknowledge_by: sessionId.data.session?.user.id,
|
||||||
|
updated_by: sessionId.data.session?.user.id,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -679,6 +711,14 @@
|
|||||||
➡️ PURCHASE ORDER
|
➡️ PURCHASE ORDER
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
{:else if col.key === "updated_at"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{new Date(row.updated_at).toLocaleString()}
|
||||||
|
</td>
|
||||||
|
{:else if col.key === "created_at"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{new Date(row.created_at).toLocaleString()}
|
||||||
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
<td class="px-4 py-2 text-gray-700"
|
<td class="px-4 py-2 text-gray-700"
|
||||||
>{row[
|
>{row[
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import Select from "svelte-select";
|
import Select from "svelte-select";
|
||||||
import { supabase } from "$lib/supabaseClient";
|
import { supabase } from "$lib/supabaseClient";
|
||||||
|
import { getSessionAuthId } from "$lib/utils/authUtil";
|
||||||
|
import { timestampToDateTime } from "$lib/utils/conversion";
|
||||||
|
|
||||||
type PurchaseOrderInsert = {
|
type PurchaseOrderInsert = {
|
||||||
issue_id: string;
|
issue_id: string;
|
||||||
@@ -10,6 +12,9 @@
|
|||||||
approved_price: number;
|
approved_price: number;
|
||||||
approved_quantity: number;
|
approved_quantity: number;
|
||||||
total_approved_order_amount?: number;
|
total_approved_order_amount?: number;
|
||||||
|
input_by?: string;
|
||||||
|
updated_by?: string;
|
||||||
|
updated_at?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let purchaseOrderInsert: PurchaseOrderInsert = {
|
let purchaseOrderInsert: PurchaseOrderInsert = {
|
||||||
@@ -19,6 +24,9 @@
|
|||||||
approved_price: 0,
|
approved_price: 0,
|
||||||
approved_quantity: 0,
|
approved_quantity: 0,
|
||||||
total_approved_order_amount: 0,
|
total_approved_order_amount: 0,
|
||||||
|
input_by: "",
|
||||||
|
updated_by: "",
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseOrders = {
|
type PurchaseOrders = {
|
||||||
@@ -42,6 +50,10 @@
|
|||||||
input_by: string;
|
input_by: string;
|
||||||
issue_id: string;
|
issue_id: string;
|
||||||
approved_by: string;
|
approved_by: string;
|
||||||
|
approved_name: string;
|
||||||
|
updated_by: string;
|
||||||
|
updated_at: string;
|
||||||
|
updated_name: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,7 +103,10 @@
|
|||||||
key: "total_approved_order_amount",
|
key: "total_approved_order_amount",
|
||||||
title: "Total Approved Order Amount",
|
title: "Total Approved Order Amount",
|
||||||
},
|
},
|
||||||
{ key: "approved_by", title: "Approved By" },
|
{ key: "approved_name", title: "Approved By" },
|
||||||
|
{ key: "inputed_name", title: "Input By" },
|
||||||
|
{ key: "updated_name", title: "Updated By" },
|
||||||
|
{ key: "updated_at", title: "Updated At" },
|
||||||
{ key: "created_at", title: "Created At" },
|
{ key: "created_at", title: "Created At" },
|
||||||
{ key: "actions", title: "Actions" }, // For edit/delete buttons
|
{ key: "actions", title: "Actions" }, // For edit/delete buttons
|
||||||
];
|
];
|
||||||
@@ -243,6 +258,10 @@
|
|||||||
"approved_by",
|
"approved_by",
|
||||||
"name",
|
"name",
|
||||||
"po_status",
|
"po_status",
|
||||||
|
"input_name",
|
||||||
|
"updated_name",
|
||||||
|
"updated_at",
|
||||||
|
"approved_name",
|
||||||
];
|
];
|
||||||
const formColumns = columns.filter(
|
const formColumns = columns.filter(
|
||||||
(col) => !excludedKeys.includes(col.key),
|
(col) => !excludedKeys.includes(col.key),
|
||||||
@@ -263,6 +282,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveProject() {
|
async function saveProject() {
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
purchaseOrderInsert = {
|
purchaseOrderInsert = {
|
||||||
issue_id: newPurchaseOrders.issue_id || "",
|
issue_id: newPurchaseOrders.issue_id || "",
|
||||||
approved_vendor: newPurchaseOrders.approved_vendor || "",
|
approved_vendor: newPurchaseOrders.approved_vendor || "",
|
||||||
@@ -272,6 +296,9 @@
|
|||||||
total_approved_order_amount:
|
total_approved_order_amount:
|
||||||
newPurchaseOrders.approved_quantity *
|
newPurchaseOrders.approved_quantity *
|
||||||
newPurchaseOrders.approved_price,
|
newPurchaseOrders.approved_price,
|
||||||
|
input_by: sessionId,
|
||||||
|
updated_by: sessionId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!validateInput()) {
|
if (!validateInput()) {
|
||||||
@@ -439,6 +466,11 @@
|
|||||||
) {
|
) {
|
||||||
const selectedOption = (e.target as HTMLSelectElement)?.value ?? "";
|
const selectedOption = (e.target as HTMLSelectElement)?.value ?? "";
|
||||||
const option = getStatusOption(selectedOption);
|
const option = getStatusOption(selectedOption);
|
||||||
|
const sessionId = await getSessionAuthId();
|
||||||
|
if (!sessionId) {
|
||||||
|
alert("You must be logged in to perform this action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
newPurchaseOrders = {
|
newPurchaseOrders = {
|
||||||
...row,
|
...row,
|
||||||
@@ -457,6 +489,9 @@
|
|||||||
.update({
|
.update({
|
||||||
approval: newPurchaseOrders.approval,
|
approval: newPurchaseOrders.approval,
|
||||||
po_status: "APPROVED",
|
po_status: "APPROVED",
|
||||||
|
approved_by: sessionId,
|
||||||
|
updated_by: sessionId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
@@ -606,61 +641,6 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
{:else if col.key === "received"}
|
|
||||||
<td class="px-4 py-2 text-center">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={row.received}
|
|
||||||
on:change={async (e) => {
|
|
||||||
const isChecked = (
|
|
||||||
e.target as HTMLInputElement
|
|
||||||
).checked;
|
|
||||||
row.received = isChecked;
|
|
||||||
|
|
||||||
if (isChecked) {
|
|
||||||
// map to project
|
|
||||||
await receivedOk(
|
|
||||||
row.id,
|
|
||||||
isChecked,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
{:else if col.key === "completed_status"}
|
|
||||||
<td class="px-4 py-2">
|
|
||||||
<select
|
|
||||||
bind:value={
|
|
||||||
row[
|
|
||||||
col.key as keyof PurchaseOrderDisplay
|
|
||||||
]
|
|
||||||
}
|
|
||||||
class="w-full p-3 border border-gray-300 rounded focus:outline-none focus:ring focus:ring-blue-500 text-gray-900"
|
|
||||||
on:change={async (e) => {
|
|
||||||
const isValue = (
|
|
||||||
e.target as HTMLInputElement
|
|
||||||
).value;
|
|
||||||
|
|
||||||
if (isValue) {
|
|
||||||
// map to project
|
|
||||||
await completedStatusOk(
|
|
||||||
row.id,
|
|
||||||
isValue,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="" disabled selected
|
|
||||||
>SELECT COMPLETE</option
|
|
||||||
>
|
|
||||||
<option value="APPROVED"
|
|
||||||
>RECEIVED COMPLETE</option
|
|
||||||
>
|
|
||||||
<option value="REJECTED"
|
|
||||||
>COMPLETE INCOMPLETE</option
|
|
||||||
>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
{:else if col.key === "actions"}
|
{:else if col.key === "actions"}
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
<button
|
<button
|
||||||
@@ -697,6 +677,42 @@
|
|||||||
➡️ PURCHASE ORDER
|
➡️ PURCHASE ORDER
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
{:else if col.key === "updated_at"}
|
||||||
|
<td class="px-4 py-2 text-gray-500"
|
||||||
|
>{timestampToDateTime(
|
||||||
|
row[
|
||||||
|
col.key as keyof PurchaseOrderDisplay
|
||||||
|
] as string,
|
||||||
|
)}</td
|
||||||
|
>
|
||||||
|
{:else if col.key === "created_at"}
|
||||||
|
<td class="px-4 py-2 text-gray-500"
|
||||||
|
>{timestampToDateTime(
|
||||||
|
row[
|
||||||
|
col.key as keyof PurchaseOrderDisplay
|
||||||
|
] as string,
|
||||||
|
)}</td
|
||||||
|
>
|
||||||
|
{:else if col.key === "po_price"}
|
||||||
|
<td class="px-4 py-2 text-gray-700"
|
||||||
|
>{row.approved_price.toLocaleString(
|
||||||
|
"en-US",
|
||||||
|
{
|
||||||
|
style: "currency",
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
)}</td
|
||||||
|
>
|
||||||
|
{:else if col.key === "po_total_price"}
|
||||||
|
<td class="px-4 py-2 text-gray-700"
|
||||||
|
>{(
|
||||||
|
row.approved_quantity *
|
||||||
|
row.approved_price
|
||||||
|
).toLocaleString("en-US", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "USD",
|
||||||
|
})}</td
|
||||||
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<td class="px-4 py-2 text-gray-700"
|
<td class="px-4 py-2 text-gray-700"
|
||||||
>{row[
|
>{row[
|
||||||
@@ -936,6 +952,22 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if col.key === "approved_by"}
|
||||||
|
<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"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
approved_by: string;
|
approved_by: string;
|
||||||
approved_price: number;
|
approved_price: number;
|
||||||
completed_status: string;
|
completed_status: string;
|
||||||
|
updated_at?: string;
|
||||||
|
updated_by?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let purchaseOrderInsert: PurchaseOrderInsert = {
|
let purchaseOrderInsert: PurchaseOrderInsert = {
|
||||||
@@ -27,6 +29,8 @@
|
|||||||
approved_by: "",
|
approved_by: "",
|
||||||
approved_price: 0,
|
approved_price: 0,
|
||||||
completed_status: "",
|
completed_status: "",
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
updated_by: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseOrders = {
|
type PurchaseOrders = {
|
||||||
@@ -50,7 +54,9 @@
|
|||||||
input_by: string;
|
input_by: string;
|
||||||
issue_id: string;
|
issue_id: string;
|
||||||
approved_by: string;
|
approved_by: string;
|
||||||
created_at: string;
|
updated_at: Date;
|
||||||
|
updated_by: string;
|
||||||
|
created_at: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseOrderDisplay = {
|
type PurchaseOrderDisplay = {
|
||||||
@@ -74,6 +80,7 @@
|
|||||||
completed_status: string;
|
completed_status: string;
|
||||||
received: boolean;
|
received: boolean;
|
||||||
received_by: string;
|
received_by: string;
|
||||||
|
created_at: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
let allRows: PurchaseOrderDisplay[] = [];
|
let allRows: PurchaseOrderDisplay[] = [];
|
||||||
@@ -96,7 +103,9 @@
|
|||||||
title: "Total Approved Order Amount",
|
title: "Total Approved Order Amount",
|
||||||
},
|
},
|
||||||
{ key: "completed_status", title: "Completed Status" },
|
{ key: "completed_status", title: "Completed Status" },
|
||||||
{ key: "acknowledge_by", title: "Acknowledged By" },
|
{ key: "acknowledged_name", title: "Acknowledged By" },
|
||||||
|
{ key: "updated_name", title: "Updated By" },
|
||||||
|
{ key: "updated_at", title: "Updated At" },
|
||||||
{ key: "created_at", title: "Created At" },
|
{ key: "created_at", title: "Created At" },
|
||||||
// { key: "actions", title: "Actions" }, // For edit/delete buttons
|
// { key: "actions", title: "Actions" }, // For edit/delete buttons
|
||||||
];
|
];
|
||||||
@@ -420,9 +429,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function completedStatusOk(id: string, status: string) {
|
async function completedStatusOk(id: string, status: string) {
|
||||||
|
const sesssionId = await supabase.auth.getSession();
|
||||||
|
if (!sesssionId.data.session) {
|
||||||
|
console.error("User not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userId = sesssionId.data.session?.user.id || "";
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ completed_status: status, po_status: status })
|
.update({
|
||||||
|
completed_status: status,
|
||||||
|
po_status: status,
|
||||||
|
updated_by: userId,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -627,6 +647,22 @@
|
|||||||
➡️ PURCHASE ORDER
|
➡️ PURCHASE ORDER
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
{:else if col.key === "updated_at"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{new Date(
|
||||||
|
row[
|
||||||
|
col.key as keyof PurchaseOrderDisplay
|
||||||
|
] as string,
|
||||||
|
).toLocaleString("en-US")}
|
||||||
|
</td>
|
||||||
|
{:else if col.key === "created_at"}
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
{new Date(
|
||||||
|
row[
|
||||||
|
col.key as keyof PurchaseOrderDisplay
|
||||||
|
] as string,
|
||||||
|
).toLocaleString("en-US")}
|
||||||
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
<td class="px-4 py-2 text-gray-700"
|
<td class="px-4 py-2 text-gray-700"
|
||||||
>{row[
|
>{row[
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
approved_by: string;
|
approved_by: string;
|
||||||
approved_price: number;
|
approved_price: number;
|
||||||
completed_status: string;
|
completed_status: string;
|
||||||
|
updated_at: Date;
|
||||||
|
updated_by: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let purchaseOrderInsert: PurchaseOrderInsert = {
|
let purchaseOrderInsert: PurchaseOrderInsert = {
|
||||||
@@ -27,6 +29,8 @@
|
|||||||
approved_by: "",
|
approved_by: "",
|
||||||
approved_price: 0,
|
approved_price: 0,
|
||||||
completed_status: "",
|
completed_status: "",
|
||||||
|
updated_at: new Date(),
|
||||||
|
updated_by: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseOrders = {
|
type PurchaseOrders = {
|
||||||
@@ -50,7 +54,9 @@
|
|||||||
input_by: string;
|
input_by: string;
|
||||||
issue_id: string;
|
issue_id: string;
|
||||||
approved_by: string;
|
approved_by: string;
|
||||||
created_at: string;
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
updated_by: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseOrderDisplay = {
|
type PurchaseOrderDisplay = {
|
||||||
@@ -74,6 +80,8 @@
|
|||||||
completed_status: string;
|
completed_status: string;
|
||||||
received: boolean;
|
received: boolean;
|
||||||
received_by: string;
|
received_by: string;
|
||||||
|
updated_at: Date;
|
||||||
|
updated_by: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let allRows: PurchaseOrderDisplay[] = [];
|
let allRows: PurchaseOrderDisplay[] = [];
|
||||||
@@ -96,7 +104,9 @@
|
|||||||
title: "Total Approved Order Amount",
|
title: "Total Approved Order Amount",
|
||||||
},
|
},
|
||||||
{ key: "received", title: "Received" },
|
{ key: "received", title: "Received" },
|
||||||
{ key: "received_by", title: "Received By" },
|
{ key: "received_name", title: "Received By" },
|
||||||
|
{ key: "updated_at", title: "Updated At" },
|
||||||
|
{ key: "updated_name", title: "Updated By" },
|
||||||
{ key: "created_at", title: "Created At" },
|
{ key: "created_at", title: "Created At" },
|
||||||
// { key: "actions", title: "Actions" }, // For edit/delete buttons
|
// { key: "actions", title: "Actions" }, // For edit/delete buttons
|
||||||
];
|
];
|
||||||
@@ -282,6 +292,8 @@
|
|||||||
approved_price: newPurchaseOrders.approved_price || "",
|
approved_price: newPurchaseOrders.approved_price || "",
|
||||||
approved_by: newPurchaseOrders.approved_by || "",
|
approved_by: newPurchaseOrders.approved_by || "",
|
||||||
completed_status: newPurchaseOrders.completed_status || "",
|
completed_status: newPurchaseOrders.completed_status || "",
|
||||||
|
updated_at: new Date(),
|
||||||
|
updated_by: (await supabase.auth.getUser()).data.user?.id || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isEditing && currentEditingId) {
|
if (isEditing && currentEditingId) {
|
||||||
@@ -421,9 +433,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function receivedOk(id: string, status: boolean) {
|
async function receivedOk(id: string, status: boolean) {
|
||||||
|
const sessionId = await supabase.auth.getSession();
|
||||||
|
if (!sessionId.data.session) {
|
||||||
|
console.error("User not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userId = sessionId.data.session?.user.id || "";
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("vb_purchase_orders")
|
.from("vb_purchase_orders")
|
||||||
.update({ received: status })
|
.update({
|
||||||
|
received: status,
|
||||||
|
received_by: userId,
|
||||||
|
updated_by: userId,
|
||||||
|
updated_at: new Date(),
|
||||||
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
total_work_hour: number;
|
total_work_hour: number;
|
||||||
remarks: string;
|
remarks: string;
|
||||||
approval: boolean;
|
approval: boolean;
|
||||||
|
approved_by?: string;
|
||||||
|
approved_date?: Date;
|
||||||
created_at?: Date;
|
created_at?: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,7 +150,7 @@
|
|||||||
} else {
|
} else {
|
||||||
form.total_work_hour = 0;
|
form.total_work_hour = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTimeSheets(
|
async function fetchTimeSheets(
|
||||||
filter: string | null = null,
|
filter: string | null = null,
|
||||||
@@ -224,8 +226,8 @@
|
|||||||
const villa = villas.find((v) => v.id === issue.villa_id);
|
const villa = villas.find((v) => v.id === issue.villa_id);
|
||||||
// Map entered_by to staff_id
|
// Map entered_by to staff_id
|
||||||
const staff = reportedBy.find((s) => s.value === issue.entered_by);
|
const staff = reportedBy.find((s) => s.value === issue.entered_by);
|
||||||
const approver = approvers?.find(u => u.id === issue.approved_by);
|
const approver = approvers?.find((u) => u.id === issue.approved_by);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: issue.id,
|
id: issue.id,
|
||||||
name: issue.work_description, // Map work_description to name
|
name: issue.work_description, // Map work_description to name
|
||||||
@@ -284,8 +286,8 @@
|
|||||||
|
|
||||||
if (!empErr && empData) {
|
if (!empErr && empData) {
|
||||||
employees = empData.map((e) => ({
|
employees = empData.map((e) => ({
|
||||||
id: e.id,
|
id: e.id,
|
||||||
name: e.employee_name,
|
name: e.employee_name,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to load employees", empErr);
|
console.error("Failed to load employees", empErr);
|
||||||
@@ -329,16 +331,19 @@
|
|||||||
currentEditingId = issue.id;
|
currentEditingId = issue.id;
|
||||||
|
|
||||||
form = {
|
form = {
|
||||||
entered_by: employees.find(e => e.name === issue.staff_id)?.id || "",
|
entered_by:
|
||||||
work_description: issue.name,
|
employees.find((e) => e.name === issue.staff_id)?.id || "",
|
||||||
type_of_work: issue.type_of_work,
|
work_description: issue.name,
|
||||||
category_of_work: issue.category_of_work,
|
type_of_work: issue.type_of_work,
|
||||||
villa_id: villas.find(v => v.villa_name === issue.villa_name)?.id || "",
|
category_of_work: issue.category_of_work,
|
||||||
datetime_in: issue.date_in?.toISOString().slice(0, 16),
|
villa_id:
|
||||||
datetime_out: issue.date_out?.toISOString().slice(0, 16),
|
villas.find((v) => v.villa_name === issue.villa_name)?.id ||
|
||||||
total_work_hour: 0,
|
"",
|
||||||
remarks: issue.remarks,
|
datetime_in: issue.date_in?.toISOString().slice(0, 16),
|
||||||
approval: null // leave null or bring in if editing allowed
|
datetime_out: issue.date_out?.toISOString().slice(0, 16),
|
||||||
|
total_work_hour: 0,
|
||||||
|
remarks: issue.remarks,
|
||||||
|
approval: null, // leave null or bring in if editing allowed
|
||||||
};
|
};
|
||||||
calculateTotalHours();
|
calculateTotalHours();
|
||||||
} else {
|
} else {
|
||||||
@@ -346,36 +351,31 @@
|
|||||||
isEditing = false;
|
isEditing = false;
|
||||||
currentEditingId = null;
|
currentEditingId = null;
|
||||||
form = {
|
form = {
|
||||||
entered_by: "",
|
entered_by: "",
|
||||||
work_description: "",
|
work_description: "",
|
||||||
type_of_work: "Running",
|
type_of_work: "Running",
|
||||||
category_of_work: "Cleaning",
|
category_of_work: "Cleaning",
|
||||||
villa_id: "",
|
villa_id: "",
|
||||||
datetime_in: "",
|
datetime_in: "",
|
||||||
datetime_out: "",
|
datetime_out: "",
|
||||||
total_work_hour: 0,
|
total_work_hour: 0,
|
||||||
remarks: "",
|
remarks: "",
|
||||||
approval: null,
|
approval: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
showModal = true;
|
showModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Employee = {
|
type Employee = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let employees: Employee[] = [];
|
let employees: Employee[] = [];
|
||||||
let villas: Villa[] = [];
|
let villas: Villa[] = [];
|
||||||
const typeOfWorkOptions: TimesheetForm["type_of_work"][] = [
|
const typeOfWorkOptions = ["Running", "Periodic", "Irregular"];
|
||||||
"Running",
|
const categoryOptions = [
|
||||||
"Periodic",
|
|
||||||
"Irregular",
|
|
||||||
];
|
|
||||||
const categoryOptions: TimesheetForm["category_of_work"][] = [
|
|
||||||
"Cleaning",
|
"Cleaning",
|
||||||
"Gardening/Pool",
|
"Gardening/Pool",
|
||||||
"Maintenance",
|
"Maintenance",
|
||||||
@@ -384,7 +384,7 @@
|
|||||||
"Administration",
|
"Administration",
|
||||||
"Non Billable",
|
"Non Billable",
|
||||||
];
|
];
|
||||||
let form: TimesheetForm = {
|
let form = {
|
||||||
entered_by: "",
|
entered_by: "",
|
||||||
work_description: "",
|
work_description: "",
|
||||||
type_of_work: "Running",
|
type_of_work: "Running",
|
||||||
@@ -440,10 +440,9 @@
|
|||||||
datetime_out: formData.get("date_out") as string,
|
datetime_out: formData.get("date_out") as string,
|
||||||
total_work_hour: newIssue.total_work_hour ?? 0,
|
total_work_hour: newIssue.total_work_hour ?? 0,
|
||||||
remarks: formData.get("remarks") as string,
|
remarks: formData.get("remarks") as string,
|
||||||
approval: null
|
approval: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from("vb_timesheet")
|
.from("vb_timesheet")
|
||||||
.insert([TimesheetsInsert]);
|
.insert([TimesheetsInsert]);
|
||||||
@@ -532,26 +531,26 @@
|
|||||||
|
|
||||||
if (isEditing && currentEditingId) {
|
if (isEditing && currentEditingId) {
|
||||||
const { error: updateError } = await supabase
|
const { error: updateError } = await supabase
|
||||||
.from("vb_timesheet")
|
.from("vb_timesheet")
|
||||||
.update({
|
.update({
|
||||||
entered_by: form.entered_by,
|
entered_by: form.entered_by,
|
||||||
work_description: form.work_description,
|
work_description: form.work_description,
|
||||||
type_of_work: form.type_of_work,
|
type_of_work: form.type_of_work,
|
||||||
category_of_work: form.category_of_work,
|
category_of_work: form.category_of_work,
|
||||||
villa_id: form.villa_id,
|
villa_id: form.villa_id,
|
||||||
datetime_in: form.datetime_in,
|
datetime_in: form.datetime_in,
|
||||||
datetime_out: form.datetime_out,
|
datetime_out: form.datetime_out,
|
||||||
total_work_hour: form.total_work_hour,
|
total_work_hour: form.total_work_hour,
|
||||||
remarks: form.remarks,
|
remarks: form.remarks,
|
||||||
approval: form.approval,
|
approval: form.approval,
|
||||||
})
|
})
|
||||||
.eq("id", currentEditingId);
|
.eq("id", currentEditingId);
|
||||||
|
|
||||||
error = updateError;
|
error = updateError;
|
||||||
} else {
|
} else {
|
||||||
const { error: insertError } = await supabase
|
const { error: insertError } = await supabase
|
||||||
.from("vb_timesheet")
|
.from("vb_timesheet")
|
||||||
.insert([form]);
|
.insert([form]);
|
||||||
|
|
||||||
error = insertError;
|
error = insertError;
|
||||||
}
|
}
|
||||||
@@ -561,22 +560,21 @@
|
|||||||
} else {
|
} else {
|
||||||
alert("Timesheet saved successfully!");
|
alert("Timesheet saved successfully!");
|
||||||
form = {
|
form = {
|
||||||
entered_by: "",
|
entered_by: "",
|
||||||
work_description: "",
|
work_description: "",
|
||||||
type_of_work: "Running",
|
type_of_work: "Running",
|
||||||
category_of_work: "Cleaning",
|
category_of_work: "Cleaning",
|
||||||
villa_id: "",
|
villa_id: "",
|
||||||
datetime_in: "",
|
datetime_in: "",
|
||||||
datetime_out: "",
|
datetime_out: "",
|
||||||
total_work_hour: 0,
|
total_work_hour: 0,
|
||||||
remarks: "",
|
remarks: "",
|
||||||
approval: null,
|
approval: null,
|
||||||
};
|
};
|
||||||
await fetchTimeSheets();
|
await fetchTimeSheets();
|
||||||
showModal = false;
|
showModal = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -699,12 +697,17 @@
|
|||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
{row[col.key] || "Not Approved"}
|
{row[col.key] || "Not Approved"}
|
||||||
</td>
|
</td>
|
||||||
{:else if col.key === "approved_date"}
|
{:else if col.key === "approved_date"}
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
{(row[col.key] && !isNaN(Date.parse(row[col.key])))
|
{row[col.key] &&
|
||||||
? new Date(row[col.key]).toLocaleDateString()
|
!isNaN(Date.parse(String(row[col.key])))
|
||||||
: "N/A"}
|
? new Date(
|
||||||
</td>
|
row[
|
||||||
|
col.key as keyof TimesheetDisplay
|
||||||
|
] as string | number | Date,
|
||||||
|
).toLocaleDateString()
|
||||||
|
: "N/A"}
|
||||||
|
</td>
|
||||||
{:else if col.key === "total_hours_work"}
|
{:else if col.key === "total_hours_work"}
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
{row[col.key].toFixed(2)} hours
|
{row[col.key].toFixed(2)} hours
|
||||||
@@ -745,20 +748,22 @@
|
|||||||
🗑️ Delete
|
🗑️ Delete
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
{:else if col.key === "date_in" || col.key === "date_out"}
|
{:else if col.key === "date_in" || col.key === "date_out"}
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
{row[col.key]
|
{row[col.key]
|
||||||
? new Date(row[col.key]).toLocaleString("en-GB", {
|
? new Date(row[col.key]).toLocaleString(
|
||||||
day: "2-digit",
|
"en-GB",
|
||||||
month: "2-digit",
|
{
|
||||||
year: "numeric",
|
day: "2-digit",
|
||||||
hour: "2-digit",
|
month: "2-digit",
|
||||||
minute: "2-digit",
|
year: "numeric",
|
||||||
hour12: false,
|
hour: "2-digit",
|
||||||
})
|
minute: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{:else}
|
{:else}
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
{row[col.key as keyof TimesheetDisplay]}
|
{row[col.key as keyof TimesheetDisplay]}
|
||||||
@@ -811,138 +816,145 @@
|
|||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
{#if showModal}
|
{#if showModal}
|
||||||
<div class="fixed inset-0 bg-black bg-opacity-50 z-50 overflow-y-auto py-10 px-4 flex justify-center items-start">
|
<div
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-50 z-50 overflow-y-auto py-10 px-4 flex justify-center items-start"
|
||||||
|
|
||||||
<form
|
|
||||||
on:submit|preventDefault={submitForm}
|
|
||||||
class="w-full max-w-lg bg-white p-6 rounded-2xl shadow-xl space-y-4"
|
|
||||||
>
|
|
||||||
<h2 class="text-2xl font-bold text-center mb-6">Timesheet Entry</h2>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="t_eb" class="block text-sm font-medium mb-1">Entered By</label
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="t_eb"
|
|
||||||
class="w-full border p-2 rounded"
|
|
||||||
bind:value={form.entered_by}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="" disabled selected>Select Employee</option>
|
|
||||||
{#each employees as employee}
|
|
||||||
<option value={employee.id}>{employee.name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="t_wd" class="block text-sm font-medium mb-1"
|
|
||||||
>Work Description</label
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
id="t_wd"
|
|
||||||
class="w-full border border-gray-300 p-2 rounded"
|
|
||||||
bind:value={form.work_description}
|
|
||||||
placeholder="Describe the work"
|
|
||||||
required
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="t_ow" class="block text-sm font-medium mb-1"
|
|
||||||
>Type of Work</label
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="t_ow"
|
|
||||||
class="w-full border p-2 rounded"
|
|
||||||
bind:value={form.type_of_work}
|
|
||||||
>
|
|
||||||
{#each typeOfWorkOptions as option}
|
|
||||||
<option value={option}>{option}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="t_cow" class="block text-sm font-medium mb-1"
|
|
||||||
>Category of Work</label
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="t_cow"
|
|
||||||
class="w-full border p-2 rounded"
|
|
||||||
bind:value={form.category_of_work}
|
|
||||||
>
|
|
||||||
{#each categoryOptions as option}
|
|
||||||
<option value={option}>{option}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="t_vn" class="block text-sm font-medium mb-1">Villa</label>
|
|
||||||
<select
|
|
||||||
id="t_vn"
|
|
||||||
class="w-full border p-2 rounded"
|
|
||||||
bind:value={form.villa_id}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="" disabled selected>Select Villa</option>
|
|
||||||
{#each villas as villa}
|
|
||||||
<option value={villa.id}>{villa.villa_name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="tdto" class="block text-sm font-medium mb-1"
|
|
||||||
>Date/Time In</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id="tdto"
|
|
||||||
type="datetime-local"
|
|
||||||
class="w-full border p-2 rounded"
|
|
||||||
bind:value={form.datetime_in}
|
|
||||||
on:change={calculateTotalHours}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="dto" class="block text-sm font-medium mb-1"
|
|
||||||
>Date/Time Out</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id="dto"
|
|
||||||
type="datetime-local"
|
|
||||||
class="w-full border p-2 rounded"
|
|
||||||
bind:value={form.datetime_out}
|
|
||||||
on:change={calculateTotalHours}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-sm">
|
|
||||||
<label for="ttwo" class="block font-medium mb-1">Total Work Hours</label>
|
|
||||||
<div id="ttwo" class="px-3 py-2">{form.total_work_hour}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="trmk" class="block text-sm font-medium mb-1">Remarks</label>
|
|
||||||
<textarea
|
|
||||||
id="trmk"
|
|
||||||
class="w-full border border-gray-300 p-2 rounded"
|
|
||||||
bind:value={form.remarks}
|
|
||||||
placeholder="Optional remarks"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition"
|
|
||||||
>
|
>
|
||||||
{isEditing ? 'Update Timesheet' : 'New Entry'}
|
<form
|
||||||
</button>
|
on:submit|preventDefault={submitForm}
|
||||||
</form>
|
class="w-full max-w-lg bg-white p-6 rounded-2xl shadow-xl space-y-4"
|
||||||
|
>
|
||||||
|
<h2 class="text-2xl font-bold text-center mb-6">Timesheet Entry</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="t_eb" class="block text-sm font-medium mb-1"
|
||||||
|
>Entered By</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="t_eb"
|
||||||
|
class="w-full border p-2 rounded"
|
||||||
|
bind:value={form.entered_by}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="" disabled selected>Select Employee</option>
|
||||||
|
{#each employees as employee}
|
||||||
|
<option value={employee.id}>{employee.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="t_wd" class="block text-sm font-medium mb-1"
|
||||||
|
>Work Description</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
id="t_wd"
|
||||||
|
class="w-full border border-gray-300 p-2 rounded"
|
||||||
|
bind:value={form.work_description}
|
||||||
|
placeholder="Describe the work"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="t_ow" class="block text-sm font-medium mb-1"
|
||||||
|
>Type of Work</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="t_ow"
|
||||||
|
class="w-full border p-2 rounded"
|
||||||
|
bind:value={form.type_of_work}
|
||||||
|
>
|
||||||
|
{#each typeOfWorkOptions as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="t_cow" class="block text-sm font-medium mb-1"
|
||||||
|
>Category of Work</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="t_cow"
|
||||||
|
class="w-full border p-2 rounded"
|
||||||
|
bind:value={form.category_of_work}
|
||||||
|
>
|
||||||
|
{#each categoryOptions as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="t_vn" class="block text-sm font-medium mb-1"
|
||||||
|
>Villa</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="t_vn"
|
||||||
|
class="w-full border p-2 rounded"
|
||||||
|
bind:value={form.villa_id}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="" disabled selected>Select Villa</option>
|
||||||
|
{#each villas as villa}
|
||||||
|
<option value={villa.id}>{villa.villa_name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="tdto" class="block text-sm font-medium mb-1"
|
||||||
|
>Date/Time In</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="tdto"
|
||||||
|
type="datetime-local"
|
||||||
|
class="w-full border p-2 rounded"
|
||||||
|
bind:value={form.datetime_in}
|
||||||
|
on:change={calculateTotalHours}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="dto" class="block text-sm font-medium mb-1"
|
||||||
|
>Date/Time Out</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="dto"
|
||||||
|
type="datetime-local"
|
||||||
|
class="w-full border p-2 rounded"
|
||||||
|
bind:value={form.datetime_out}
|
||||||
|
on:change={calculateTotalHours}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-sm">
|
||||||
|
<label for="ttwo" class="block font-medium mb-1"
|
||||||
|
>Total Work Hours</label
|
||||||
|
>
|
||||||
|
<div id="ttwo" class="px-3 py-2">{form.total_work_hour}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="trmk" class="block text-sm font-medium mb-1"
|
||||||
|
>Remarks</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
id="trmk"
|
||||||
|
class="w-full border border-gray-300 p-2 rounded"
|
||||||
|
bind:value={form.remarks}
|
||||||
|
placeholder="Optional remarks"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition"
|
||||||
|
>
|
||||||
|
{isEditing ? "Update Timesheet" : "New Entry"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
176
src/routes/backoffice/vendor/+page.svelte
vendored
176
src/routes/backoffice/vendor/+page.svelte
vendored
@@ -28,18 +28,6 @@
|
|||||||
website: string;
|
website: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let allRowsVendor: Vendor[] = [];
|
|
||||||
let currentPage = 1;
|
|
||||||
let itemsPerPage = 10;
|
|
||||||
$: offset = (currentPage - 1) * itemsPerPage;
|
|
||||||
$: totalPages = Math.ceil(totalItems / itemsPerPage);
|
|
||||||
let totalItems = 0;
|
|
||||||
let newVendor: Record<string, any> = {};
|
|
||||||
let showModal = false;
|
|
||||||
let isEditing = false;
|
|
||||||
let currentEditingId: string | null = null;
|
|
||||||
let searchTerm = "";
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: "name", title: "Name" },
|
{ key: "name", title: "Name" },
|
||||||
{ key: "vendor_type", title: "Vendor Type" },
|
{ key: "vendor_type", title: "Vendor Type" },
|
||||||
@@ -63,6 +51,19 @@
|
|||||||
const excludedKeys = ["id", "created_by", "created_at", "updated_at"];
|
const excludedKeys = ["id", "created_by", "created_at", "updated_at"];
|
||||||
$: formColumns = columns.filter((col) => !excludedKeys.includes(col.key));
|
$: formColumns = columns.filter((col) => !excludedKeys.includes(col.key));
|
||||||
|
|
||||||
|
let allRowsVendor: Vendor[] = [];
|
||||||
|
let currentPage = 1;
|
||||||
|
let offset = 0;
|
||||||
|
let itemsPerPage = 10;
|
||||||
|
let totalItems = 0;
|
||||||
|
let newVendor: Record<string, any> = {};
|
||||||
|
let showModal = false;
|
||||||
|
let isEditing = false;
|
||||||
|
let currentEditingId: string | null = null;
|
||||||
|
let searchTerm = "";
|
||||||
|
$: offset = (currentPage - 1) * itemsPerPage;
|
||||||
|
$: totalPages = Math.ceil(totalItems / itemsPerPage);
|
||||||
|
|
||||||
async function fetchVendor(search = "", resetPage = false) {
|
async function fetchVendor(search = "", resetPage = false) {
|
||||||
if (resetPage) currentPage = 1;
|
if (resetPage) currentPage = 1;
|
||||||
|
|
||||||
@@ -84,7 +85,13 @@
|
|||||||
|
|
||||||
function resetPagination() {
|
function resetPagination() {
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
|
offset = 0;
|
||||||
|
totalItems = 0;
|
||||||
fetchVendor(searchTerm);
|
fetchVendor(searchTerm);
|
||||||
|
showModal = false;
|
||||||
|
isEditing = false;
|
||||||
|
currentEditingId = null;
|
||||||
|
newVendor = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextPage() {
|
function nextPage() {
|
||||||
@@ -102,6 +109,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changePage(page: number) {
|
function changePage(page: number) {
|
||||||
|
if (page < 1 || page > totalPages || page === currentPage) return;
|
||||||
currentPage = page;
|
currentPage = page;
|
||||||
fetchVendor(searchTerm);
|
fetchVendor(searchTerm);
|
||||||
}
|
}
|
||||||
@@ -159,7 +167,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteVendor(id: string) {
|
async function deleteVendor(id: string) {
|
||||||
const { error } = await supabase.from("vb_vendor").delete().eq("id", id);
|
const { error } = await supabase
|
||||||
|
.from("vb_vendor")
|
||||||
|
.delete()
|
||||||
|
.eq("id", id);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Delete error:", error);
|
console.error("Delete error:", error);
|
||||||
} else {
|
} else {
|
||||||
@@ -167,7 +178,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pageRange(totalPages: number, currentPage: number): (number | string)[] {
|
function pageRange(
|
||||||
|
totalPages: number,
|
||||||
|
currentPage: number,
|
||||||
|
): (number | string)[] {
|
||||||
const range: (number | string)[] = [];
|
const range: (number | string)[] = [];
|
||||||
const maxDisplay = 5;
|
const maxDisplay = 5;
|
||||||
|
|
||||||
@@ -207,79 +221,125 @@
|
|||||||
fetchVendor(searchTerm, true);
|
fetchVendor(searchTerm, true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<button class="bg-blue-600 text-white px-4 py-2 rounded" on:click={() => openModal()}>
|
<button
|
||||||
|
class="bg-blue-600 text-white px-4 py-2 rounded"
|
||||||
|
on:click={() => openModal()}
|
||||||
|
>
|
||||||
➕ Add Vendor
|
➕ Add Vendor
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Table -->
|
<!-- Table -->
|
||||||
<table class="min-w-[1000px] divide-y divide-gray-200 text-sm w-max">
|
<div class="overflow-x-auto rounded-lg shadow mb-4">
|
||||||
<thead class="bg-gray-100">
|
<table class="min-w-[1000px] divide-y divide-gray-200 text-sm w-max">
|
||||||
<tr>
|
<thead class="bg-gray-100">
|
||||||
{#each columns as col}
|
<tr>
|
||||||
<th class="p-2 text-left border">{col.title}</th>
|
|
||||||
{/each}
|
|
||||||
<th class="p-2 border">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each allRowsVendor as row}
|
|
||||||
<tr class="hover:bg-gray-50">
|
|
||||||
{#each columns as col}
|
{#each columns as col}
|
||||||
<td class="p-2 border">{row[col.key]}</td>
|
<th class="p-2 text-left border">{col.title}</th>
|
||||||
{/each}
|
{/each}
|
||||||
<td class="p-2 border">
|
<th class="p-2 border">Actions</th>
|
||||||
<button class="text-blue-600" on:click={() => openModal(row)}>✏️</button>
|
|
||||||
<button class="text-red-600 ml-2" on:click={() => deleteVendor(row.id)}>🗑️</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{#each allRowsVendor as row}
|
||||||
|
<tr class="hover:bg-gray-50">
|
||||||
|
{#each columns as col}
|
||||||
|
<td class="p-2 border"
|
||||||
|
>{row[col.key as keyof typeof row]}</td
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
<td class="p-2 border">
|
||||||
|
<button
|
||||||
|
class="text-blue-600"
|
||||||
|
on:click={() => openModal(row)}>✏️</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="text-red-600 ml-2"
|
||||||
|
on:click={() => deleteVendor(row.id)}>🗑️</button
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
<div class="flex justify-between items-center mt-4 text-sm">
|
<div class="flex justify-between items-center mt-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
Showing {(currentPage - 1) * itemsPerPage + 1}–
|
Page {currentPage} of {totalPages} | Showing
|
||||||
{Math.min(currentPage * itemsPerPage, totalItems)} of {totalItems}
|
{currentPage === totalPages && totalItems > 0
|
||||||
|
? totalItems - offset
|
||||||
|
: itemsPerPage} items | Showing
|
||||||
|
{offset + 1} to {Math.min(currentPage * itemsPerPage, totalItems)} of {totalItems}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex space-x-1">
|
<div class="flex space-x-1">
|
||||||
<button on:click={previousPage} disabled={currentPage === 1} class="px-2 py-1 border rounded disabled:opacity-50">Prev</button>
|
<button
|
||||||
|
on:click={previousPage}
|
||||||
|
disabled={currentPage <= 1}
|
||||||
|
class="px-2 py-1 border rounded disabled:opacity-50">Prev</button
|
||||||
|
>
|
||||||
{#each pageRange(totalPages, currentPage) as page}
|
{#each pageRange(totalPages, currentPage) as page}
|
||||||
{#if page === '...'}
|
{#if page === "..."}
|
||||||
<span class="px-2">...</span>
|
<span class="px-2">...</span>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
on:click={() => changePage(page)}
|
on:click={() => changePage(page as number)}
|
||||||
class="px-2 py-1 border rounded {page === currentPage ? 'bg-blue-600 text-white' : ''}">
|
class="px-2 py-1 border rounded {page === currentPage
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
{page}
|
{page}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<button on:click={nextPage} disabled={currentPage === totalPages} class="px-2 py-1 border rounded disabled:opacity-50">Next</button>
|
<button
|
||||||
|
on:click={nextPage}
|
||||||
|
disabled={currentPage >= totalPages}
|
||||||
|
class="px-2 py-1 border rounded disabled:opacity-50">Next</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
{#if showModal}
|
{#if showModal}
|
||||||
<div class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50">
|
<div
|
||||||
<div class="bg-white p-6 rounded w-[500px] max-h-[90vh] overflow-y-auto">
|
class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50"
|
||||||
<h2 class="text-lg font-semibold mb-4">{isEditing ? "Edit Vendor" : "Add Vendor"}</h2>
|
>
|
||||||
{#each formColumns as col}
|
<div
|
||||||
<div class="mb-3">
|
class="bg-white p-6 rounded w-[500px] max-h-[90vh] overflow-y-auto"
|
||||||
<label class="text-sm">{col.title}</label>
|
>
|
||||||
{#if col.key === 'vendor_unique'}
|
<h2 class="text-lg font-semibold mb-4">
|
||||||
<input class="w-full p-2 border rounded bg-gray-100" bind:value={newVendor[col.key]} readonly />
|
{isEditing ? "Edit Vendor" : "Add Vendor"}
|
||||||
{:else}
|
</h2>
|
||||||
<input class="w-full p-2 border rounded" bind:value={newVendor[col.key]} />
|
{#each formColumns as col}
|
||||||
{/if}
|
<div class="mb-3">
|
||||||
|
<label class="text-sm">{col.title}</label>
|
||||||
|
{#if col.key === "vendor_unique"}
|
||||||
|
<input
|
||||||
|
class="w-full p-2 border rounded bg-gray-100"
|
||||||
|
bind:value={newVendor[col.key]}
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
class="w-full p-2 border rounded"
|
||||||
|
bind:value={newVendor[col.key]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="flex justify-end mt-4 space-x-2">
|
||||||
|
<button
|
||||||
|
class="px-4 py-2 bg-gray-200 rounded"
|
||||||
|
on:click={() => (showModal = false)}>Cancel</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="px-4 py-2 bg-blue-600 text-white rounded"
|
||||||
|
on:click={isEditing ? updateVendor : addVendor}>Save</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
|
||||||
<div class="flex justify-end mt-4 space-x-2">
|
|
||||||
<button class="px-4 py-2 bg-gray-200 rounded" on:click={() => showModal = false}>Cancel</button>
|
|
||||||
<button class="px-4 py-2 bg-blue-600 text-white rounded" on:click={isEditing ? updateVendor : addVendor}>Save</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user