22 KiB
22 KiB
<script lang="ts">
import { supabase } from "$lib/supabaseClient";
import { onMount } from "svelte";
import { writable } from "svelte/store";
type Transport = {
id: string;
guest_name: string;
requested_date: Date;
area: string;
pickup_date: Date;
request_things: string;
additional_notes: string;
requested_by: string;
vendor_email_address: string;
created_at?: Date;
};
type TransportDisplay = {
guest_name: string;
requested_date: Date;
area: string;
pickup_date: Date;
request_things: string;
additional_notes: string;
requested_by: string;
vendor_email_address: string;
created_at?: Date;
};
const area = [
{ label: "Laksamana", value: "Laksamana" },
{ label: "Drupadi", value: "Drupadi" },
{ label: "Abimanyu", value: "Abimanyu" },
{ label: "Seminyak", value: "Seminyak" },
];
const requestThings = [
{ label: "Airport pickup", value: "Airport pickup" },
{ label: "Airport transfer", value: "Airport transfer" },
{ label: "Day car charter", value: "Day car charter" },
{ label: "One way drop off", value: "One way drop off" },
{ label: "One way pickup", value: "One way pickup" },
{ label: "Other", value: "Other" },
];
let allRows: Transport[] = [];
type columns = {
key: string;
title: string;
};
const columns: columns[] = [
{ key: "guest_name", title: "Guest Name" },
{ key: "requested_date", title: "Requested Date" },
{ key: "area", title: "Area" },
{ key: "pickup_date", title: "Pickup Date" },
{ key: "request_things", title: "Request Things" },
{ key: "additional_notes", title: "Additional Notes" },
{ key: "requested_by", title: "Requested By" },
{ key: "vendor_email_address", title: "Vendor Email Address" },
{ key: "created_at", title: "Created At" },
{ key: "actions", title: "Actions" },
];
async function fetchTransport(
filter: string | null = null,
searchTerm: string | null = null,
sortColumn: string | null = "created_at",
sortOrder: "asc" | "desc" = "desc",
offset: number = 0,
limit: number = 10,
) {
let query = supabase
.from("vb_transport")
.select("*", { count: "exact" })
.order(sortColumn || "created_at", {
ascending: sortOrder === "asc",
});
if (filter) {
query = query.eq("area", filter);
}
if (searchTerm) {
query = query.ilike("guest_name", `%${searchTerm}%`);
}
if (offset) {
query = query.range(offset, offset + limit - 1);
}
if (limit) {
query = query.limit(limit);
}
const { data: transportResponse, error } = await query;
if (error) {
console.error("Error fetching timesheets:", error);
return;
}
allRows = transportResponse.map((row) => ({
...row,
requested_date: new Date(row.requested_date),
pickup_date: new Date(row.pickup_date),
created_at: row.created_at ? new Date(row.created_at) : undefined,
})) as Transport[];
}
let currentPage = 1;
const rowsPerPage = 10;
const totalRows = allRows.length;
const totalPages = Math.ceil(totalRows / rowsPerPage);
$: paginatedRows = allRows.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage,
);
console.log("Total Rows:", totalRows);
function goToPage(page: number) {
if (page >= 1 && page <= totalPages) currentPage = page;
fetchTransport(
null,
null,
"created_at",
"desc",
(currentPage - 1) * rowsPerPage,
rowsPerPage,
);
}
onMount(() => {
fetchTransport();
});
// Initialize the first page
$: currentPage = 1;
let showModal = false;
let isEditing = false;
let currentEditingId: string | null = null;
let newTransport: Transport = {
id: "",
guest_name: "",
requested_date: new Date(),
area: "",
pickup_date: new Date(),
request_things: "",
additional_notes: "",
requested_by: "",
vendor_email_address: "",
};
const excludedKeys = ["id", "actions", "created_at"];
const formColumns = columns.filter(
(col) => !excludedKeys.includes(col.key),
);
function openModal(transport?: Transport) {
if (transport) {
isEditing = true;
currentEditingId = transport.id;
newTransport = { ...transport };
} else {
isEditing = false;
currentEditingId = null;
newTransport = {
id: "",
guest_name: "",
requested_date: new Date(),
area: "",
pickup_date: new Date(),
request_things: "",
additional_notes: "",
requested_by: "",
vendor_email_address: "",
};
}
showModal = true;
}
async function saveIssue(event: Event) {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
// Validate form data
if (!validateForm(formData)) {
console.error("Form validation failed");
return;
}
if (isEditing && currentEditingId) {
const { error } = await supabase
.from("vb_tansport")
.update(newTransport)
.eq("id", currentEditingId);
if (error) {
alert("Error updating transport: " + error.message);
console.error("Error updating transport:", error);
return;
}
}
await fetchTransport();
showModal = false;
}
async function deleteTransport(id: string) {
if (confirm("Are you sure you want to delete this transport?")) {
const { error } = await supabase
.from("vb_transport")
.delete()
.eq("id", id);
if (error) {
console.error("Error deleting issue:", error);
return;
}
await fetchTransport();
}
}
export let formErrors = writable<{ [key: string]: string }>({});
function validateForm(formData: FormData): boolean {
const errors: { [key: string]: string } = {};
const requiredFields = [
"guest_name",
"requested_date",
"area",
"pickup_date",
"request_things",
"additional_notes",
"requested_by",
"vendor_email_address",
];
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";
}
</script>
{/if}
🚗 Transport Request List
Manage and track all transport requests efficiently.
{
const searchTerm = (
e.target as HTMLInputElement
).value.toLowerCase();
fetchTransport(null, searchTerm, "created_at", "desc");
}}
/>
{
const filter = (e.target as HTMLSelectElement).value;
fetchTransport(filter, null, null, "desc");
}}
>
All Area
{#each area as a}
{a.label}
{/each}
{
const filter = (e.target as HTMLSelectElement).value;
fetchTransport(null, filter, "created_at", "desc");
}}
>
All Requests
{#each requestThings as r}
{r.label}
{/each}
fetchTransport(null, null, "created_at", "desc", 0, 10)}
>
🔄 Reset
{#each columns as col}
{#if col.key === "guest_name"}
{:else}
{/if}
{/each}
{#each paginatedRows as row}
{#each columns as col}
{#if col.key === "guest_name"}
{:else if col.key === "requested_date"}
{:else if col.key === "pickup_date"}
{:else if col.key === "created_at"}
{:else if col.key === "area"}
{:else if col.key === "request_things"}
{:else if col.key === "additional_notes"}
{:else if col.key === "requested_by"}
{:else if col.key === "vendor_email_address"}
{:else if col.key === "actions"}
{:else}
{/if}
{/each}
{/each}
| {col.title} | {col.title} | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| {row[col.key as keyof Transport]} | {row[col.key as keyof Transport] ? new Date( row[ col.key as keyof Transport ] as string | number | Date, ).toLocaleDateString() : "N/A"} | {new Date( new Date( row[col.key as keyof Transport] as | string | number | Date, ).toLocaleDateString() || "N/A", ).toLocaleDateString()} | {row[col.key as keyof Transport] ? new Date( row[ col.key as keyof Transport ] as string | number | Date, ).toLocaleDateString() : "N/A"} | {row[col.key as keyof Transport]} | {row[col.key as keyof Transport]} | {row[col.key as keyof Transport] || "No additional notes"} | {row[col.key as keyof Transport]} | {row[col.key as keyof TransportDisplay] || "No email provided"} | openModal(row)} > ✏️ Edit deleteTransport(row.id)} > 🗑️ Delete | {row[col.key as keyof TransportDisplay]} |
<!-- Pagination controls -->
<div class="flex justify-between items-center text-sm">
<div>
Showing {(currentPage - 1) * rowsPerPage + 1}–
{Math.min(currentPage * rowsPerPage, allRows.length)} of {allRows.length}
</div>
<div class="space-x-2">
<button
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
on:click={() => goToPage(currentPage - 1)}
disabled={currentPage === 1}
>
Previous
</button>
{#each Array(totalPages)
.fill(0)
.map((_, i) => i + 1) as page}
<button
class="px-3 py-1 rounded border text-sm
{currentPage === page
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white border-gray-300 hover:bg-gray-100'}"
on:click={() => goToPage(page)}
>
{page}
</button>
{/each}
<button
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
on:click={() => goToPage(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</div>
{#if showModal}
{isEditing ? "Edit Transport Request" : "New Transport Request"}
{#each formColumns as col}
{#if col.key === "requested_date" || col.key === "pickup_date"}
{:else if col.key === "area"}
Select Area
{#each area as a}
{a.label}
{/each}
{:else if col.key === "request_things"}
Select Request
{#each requestThings as r}
{r.label}
{/each}
{:else}
<input
type="text"
name={col.key}
bind:value={
newTransport[col.key as keyof Transport]
}
placeholder={col.title}
class="w-full border rounded-xl px-4 py-
2 focus:outline-none focus:ring-2 focus:ring-purple-400 {errorClass(col.key)}"
/>
{/if}
{#if $formErrors[col.key]}
{/each}
{$formErrors[col.key]}
{/if}
Additional Notes
{#if $formErrors.additional_notes}
{$formErrors.additional_notes}
{/if}
<button
type="button"
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-xl hover:bg-gray-300"
on:click={() => (showModal = false)}
>
Cancel
{isEditing ? "Update" : "Create"}