perbaikan data

This commit is contained in:
aji@catalis.app
2025-06-22 12:26:43 +07:00
parent 2e4ff82e98
commit 2ad0f5093d
10 changed files with 752 additions and 353 deletions

View File

@@ -22,6 +22,8 @@
total_work_hour: number;
remarks: string;
approval: boolean;
approved_by?: string;
approved_date?: Date;
created_at?: Date;
};
@@ -148,7 +150,7 @@
} else {
form.total_work_hour = 0;
}
}
}
async function fetchTimeSheets(
filter: string | null = null,
@@ -224,8 +226,8 @@
const villa = villas.find((v) => v.id === issue.villa_id);
// Map entered_by to staff_id
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 {
id: issue.id,
name: issue.work_description, // Map work_description to name
@@ -284,8 +286,8 @@
if (!empErr && empData) {
employees = empData.map((e) => ({
id: e.id,
name: e.employee_name,
id: e.id,
name: e.employee_name,
}));
} else {
console.error("Failed to load employees", empErr);
@@ -329,16 +331,19 @@
currentEditingId = issue.id;
form = {
entered_by: employees.find(e => e.name === issue.staff_id)?.id || "",
work_description: issue.name,
type_of_work: issue.type_of_work,
category_of_work: issue.category_of_work,
villa_id: villas.find(v => v.villa_name === issue.villa_name)?.id || "",
datetime_in: issue.date_in?.toISOString().slice(0, 16),
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
entered_by:
employees.find((e) => e.name === issue.staff_id)?.id || "",
work_description: issue.name,
type_of_work: issue.type_of_work,
category_of_work: issue.category_of_work,
villa_id:
villas.find((v) => v.villa_name === issue.villa_name)?.id ||
"",
datetime_in: issue.date_in?.toISOString().slice(0, 16),
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();
} else {
@@ -346,36 +351,31 @@
isEditing = false;
currentEditingId = null;
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,
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,
};
}
showModal = true;
}
type Employee = {
id: string;
name: string;
};
let employees: Employee[] = [];
let villas: Villa[] = [];
const typeOfWorkOptions: TimesheetForm["type_of_work"][] = [
"Running",
"Periodic",
"Irregular",
];
const categoryOptions: TimesheetForm["category_of_work"][] = [
const typeOfWorkOptions = ["Running", "Periodic", "Irregular"];
const categoryOptions = [
"Cleaning",
"Gardening/Pool",
"Maintenance",
@@ -384,7 +384,7 @@
"Administration",
"Non Billable",
];
let form: TimesheetForm = {
let form = {
entered_by: "",
work_description: "",
type_of_work: "Running",
@@ -440,10 +440,9 @@
datetime_out: formData.get("date_out") as string,
total_work_hour: newIssue.total_work_hour ?? 0,
remarks: formData.get("remarks") as string,
approval: null
approval: null,
};
const { error } = await supabase
.from("vb_timesheet")
.insert([TimesheetsInsert]);
@@ -532,26 +531,26 @@
if (isEditing && currentEditingId) {
const { error: updateError } = await supabase
.from("vb_timesheet")
.update({
entered_by: form.entered_by,
work_description: form.work_description,
type_of_work: form.type_of_work,
category_of_work: form.category_of_work,
villa_id: form.villa_id,
datetime_in: form.datetime_in,
datetime_out: form.datetime_out,
total_work_hour: form.total_work_hour,
remarks: form.remarks,
approval: form.approval,
})
.eq("id", currentEditingId);
.from("vb_timesheet")
.update({
entered_by: form.entered_by,
work_description: form.work_description,
type_of_work: form.type_of_work,
category_of_work: form.category_of_work,
villa_id: form.villa_id,
datetime_in: form.datetime_in,
datetime_out: form.datetime_out,
total_work_hour: form.total_work_hour,
remarks: form.remarks,
approval: form.approval,
})
.eq("id", currentEditingId);
error = updateError;
} else {
const { error: insertError } = await supabase
.from("vb_timesheet")
.insert([form]);
.from("vb_timesheet")
.insert([form]);
error = insertError;
}
@@ -561,22 +560,21 @@
} else {
alert("Timesheet saved 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,
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,
};
await fetchTimeSheets();
showModal = false;
}
}
</script>
<div>
@@ -699,12 +697,17 @@
<td class="px-4 py-2">
{row[col.key] || "Not Approved"}
</td>
{:else if col.key === "approved_date"}
<td class="px-4 py-2">
{(row[col.key] && !isNaN(Date.parse(row[col.key])))
? new Date(row[col.key]).toLocaleDateString()
: "N/A"}
</td>
{:else if col.key === "approved_date"}
<td class="px-4 py-2">
{row[col.key] &&
!isNaN(Date.parse(String(row[col.key])))
? new Date(
row[
col.key as keyof TimesheetDisplay
] as string | number | Date,
).toLocaleDateString()
: "N/A"}
</td>
{:else if col.key === "total_hours_work"}
<td class="px-4 py-2">
{row[col.key].toFixed(2)} hours
@@ -745,20 +748,22 @@
🗑️ Delete
</button>
</td>
{:else if col.key === "date_in" || col.key === "date_out"}
<td class="px-4 py-2">
{row[col.key]
? new Date(row[col.key]).toLocaleString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
{:else if col.key === "date_in" || col.key === "date_out"}
<td class="px-4 py-2">
{row[col.key]
? new Date(row[col.key]).toLocaleString(
"en-GB",
{
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
},
)
: "N/A"}
</td>
</td>
{:else}
<td class="px-4 py-2">
{row[col.key as keyof TimesheetDisplay]}
@@ -811,138 +816,145 @@
<!-- Modal -->
{#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">
<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"
<div
class="fixed inset-0 bg-black bg-opacity-50 z-50 overflow-y-auto py-10 px-4 flex justify-center items-start"
>
{isEditing ? 'Update Timesheet' : 'New Entry'}
</button>
</form>
<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"}
</button>
</form>
</div>
{/if}