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