add purchase page
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { supabase } from "$lib/supabaseClient";
|
||||
import { onMount } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import Pagination from "$lib/Pagination.svelte";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
type Timesheets = {
|
||||
@@ -383,7 +381,11 @@
|
||||
new Date(tsdata.datetime_out).getTime() -
|
||||
new Date(tsdata.datetime_in).getTime(),
|
||||
) / (1000 * 60 * 60),
|
||||
approved_by: tsdata.approved_name || "Not Approved",
|
||||
approved_by: tsdata.approved_name?.trim()
|
||||
? tsdata.approved_name
|
||||
: tsdata.approval === true
|
||||
? "Auto Approve"
|
||||
: "Not Approved",
|
||||
approved_date: tsdata.approved_date
|
||||
? new Date(tsdata.approved_date)
|
||||
: undefined,
|
||||
|
||||
274
src/routes/purchaseorder/+page.svelte
Normal file
274
src/routes/purchaseorder/+page.svelte
Normal file
@@ -0,0 +1,274 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { supabase } from "$lib/supabaseClient";
|
||||
import logo from "$lib/images/logo.webp";
|
||||
|
||||
type TimesheetForm = {
|
||||
entered_by: string;
|
||||
work_description: string;
|
||||
type_of_work: "Running" | "Periodic" | "Irregular";
|
||||
category_of_work:
|
||||
| "Cleaning"
|
||||
| "Gardening/Pool"
|
||||
| "Maintenance"
|
||||
| "Supervision"
|
||||
| "Guest Service"
|
||||
| "Administration"
|
||||
| "Non Billable";
|
||||
villa_id: string;
|
||||
datetime_in: string;
|
||||
datetime_out: string;
|
||||
total_work_hour: number;
|
||||
remarks: string;
|
||||
approval: boolean | null; // Allow null for new entries
|
||||
};
|
||||
|
||||
type Villa = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Employee = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
let employees: Employee[] = [];
|
||||
let villas: Villa[] = [];
|
||||
|
||||
let form: TimesheetForm = {
|
||||
entered_by: "",
|
||||
work_description: "",
|
||||
type_of_work: "Running",
|
||||
category_of_work: "Cleaning",
|
||||
villa_id: "",
|
||||
datetime_in: "",
|
||||
datetime_out: "",
|
||||
total_work_hour: 0,
|
||||
remarks: "",
|
||||
approval: null, // Default null
|
||||
};
|
||||
|
||||
const typeOfWorkOptions: TimesheetForm["type_of_work"][] = [
|
||||
"Running",
|
||||
"Periodic",
|
||||
"Irregular",
|
||||
];
|
||||
const categoryOptions: TimesheetForm["category_of_work"][] = [
|
||||
"Cleaning",
|
||||
"Gardening/Pool",
|
||||
"Maintenance",
|
||||
"Supervision",
|
||||
"Guest Service",
|
||||
"Administration",
|
||||
"Non Billable",
|
||||
];
|
||||
|
||||
onMount(async () => {
|
||||
// Fetch villas
|
||||
const { data: villaData, error: villaError } = await supabase
|
||||
.from("vb_villas")
|
||||
.select("id, villa_name, villa_status")
|
||||
.eq("villa_status", "Active")
|
||||
.order("villa_name", { ascending: true });
|
||||
|
||||
if (villaError) {
|
||||
console.error("Failed to fetch villas:", villaError.message);
|
||||
} else if (villaData) {
|
||||
villas = villaData.map((v) => ({ id: v.id, name: v.villa_name }));
|
||||
}
|
||||
|
||||
// Fetch employees
|
||||
const { data: empData, error: empError } = await supabase
|
||||
.from("vb_employee")
|
||||
.select("id, employee_name")
|
||||
.eq("employee_status", "Active")
|
||||
.order("employee_name", { ascending: true });
|
||||
|
||||
if (empError) {
|
||||
console.error("Failed to fetch employees:", empError.message);
|
||||
} else if (empData) {
|
||||
employees = empData.map((e) => ({ id: e.id, name: e.employee_name }));
|
||||
}
|
||||
});
|
||||
|
||||
function calculateTotalHours() {
|
||||
if (form.datetime_in && form.datetime_out) {
|
||||
const start = new Date(form.datetime_in);
|
||||
const end = new Date(form.datetime_out);
|
||||
const diffInMs = end.getTime() - start.getTime();
|
||||
const hours = diffInMs / (1000 * 60 * 60);
|
||||
form.total_work_hour = Math.max(Number(hours.toFixed(2)), 0);
|
||||
} else {
|
||||
form.total_work_hour = 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
calculateTotalHours();
|
||||
|
||||
if (!form.entered_by) {
|
||||
alert("Please select an employee.");
|
||||
return;
|
||||
}
|
||||
if (!form.villa_id) {
|
||||
alert("Please select a villa.");
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await supabase.from("vb_timesheet").insert([form]);
|
||||
|
||||
if (error) {
|
||||
alert("Failed to submit timesheet: " + error.message);
|
||||
} else {
|
||||
alert("Timesheet submitted successfully!");
|
||||
form = {
|
||||
entered_by: "",
|
||||
work_description: "",
|
||||
type_of_work: "Running",
|
||||
category_of_work: "Cleaning",
|
||||
villa_id: "",
|
||||
datetime_in: "",
|
||||
datetime_out: "",
|
||||
total_work_hour: 0,
|
||||
remarks: "",
|
||||
approval: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||
<form
|
||||
on:submit|preventDefault={submitForm}
|
||||
class="w-full max-w-lg bg-white p-6 rounded-2xl shadow-xl space-y-4"
|
||||
>
|
||||
<img src={logo} alt="Villa Logo" class="mx-auto mb-6" width="250" />
|
||||
|
||||
<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.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"
|
||||
>
|
||||
Submit Timesheet
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -20,6 +20,7 @@
|
||||
datetime_out: string;
|
||||
total_work_hour: number;
|
||||
remarks: string;
|
||||
approved_date: string | null;
|
||||
approval: boolean | null; // Allow null for new entries
|
||||
};
|
||||
|
||||
@@ -46,9 +47,9 @@
|
||||
datetime_out: "",
|
||||
total_work_hour: 0,
|
||||
remarks: "",
|
||||
approved_date: null,
|
||||
approval: null, // Default null
|
||||
};
|
||||
|
||||
const typeOfWorkOptions: TimesheetForm["type_of_work"][] = [
|
||||
"Running",
|
||||
"Periodic",
|
||||
@@ -115,7 +116,16 @@
|
||||
alert("Please select a villa.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (form.approval) {
|
||||
const today = new Date();
|
||||
const yyyy = today.getFullYear();
|
||||
const mm = String(today.getMonth() + 1).padStart(2, "0");
|
||||
const dd = String(today.getDate()).padStart(2, "0");
|
||||
form.approved_date = `${yyyy}/${mm}/${dd}`;
|
||||
} else {
|
||||
form.approved_date = null;
|
||||
}
|
||||
form.approval = form.total_work_hour <= 1;
|
||||
const { error } = await supabase.from("vb_timesheet").insert([form]);
|
||||
|
||||
if (error) {
|
||||
@@ -263,7 +273,6 @@
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user