add relation timesheet -> employee
This commit is contained in:
@@ -1,188 +1,241 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { supabase } from '$lib/supabaseClient';
|
import { supabase } from '$lib/supabaseClient';
|
||||||
|
|
||||||
type TimesheetForm = {
|
type TimesheetForm = {
|
||||||
entered_by: string;
|
entered_by: string;
|
||||||
work_description: string;
|
work_description: string;
|
||||||
type_of_work: 'Running' | 'Periodic' | 'Irregular';
|
type_of_work: 'Running' | 'Periodic' | 'Irregular';
|
||||||
category_of_work:
|
category_of_work:
|
||||||
| 'Cleaning'
|
| 'Cleaning'
|
||||||
| 'Gardening/Pool'
|
| 'Gardening/Pool'
|
||||||
| 'Maintenance'
|
| 'Maintenance'
|
||||||
| 'Supervision'
|
| 'Supervision'
|
||||||
| 'Guest Service'
|
| 'Guest Service'
|
||||||
| 'Administration'
|
| 'Administration'
|
||||||
| 'Non Billable';
|
| 'Non Billable';
|
||||||
villa_id: string;
|
villa_id: string;
|
||||||
datetime_in: string;
|
datetime_in: string;
|
||||||
datetime_out: string;
|
datetime_out: string;
|
||||||
total_work_hour: number;
|
total_work_hour: number;
|
||||||
remarks: string;
|
remarks: string;
|
||||||
approval: boolean;
|
approval: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Villa = {
|
type Villa = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let villas: Villa[] = [];
|
type Employee = {
|
||||||
|
id: string;
|
||||||
let form: TimesheetForm = {
|
name: string;
|
||||||
entered_by: '',
|
};
|
||||||
work_description: '',
|
|
||||||
type_of_work: 'Running',
|
let employees: Employee[] = [];
|
||||||
category_of_work: 'Cleaning',
|
let villas: Villa[] = [];
|
||||||
villa_id: '',
|
|
||||||
datetime_in: '',
|
let form: TimesheetForm = {
|
||||||
datetime_out: '',
|
entered_by: '',
|
||||||
total_work_hour: 0,
|
work_description: '',
|
||||||
remarks: '',
|
type_of_work: 'Running',
|
||||||
approval: false
|
category_of_work: 'Cleaning',
|
||||||
};
|
villa_id: '',
|
||||||
|
datetime_in: '',
|
||||||
const typeOfWorkOptions: TimesheetForm['type_of_work'][] = ['Running', 'Periodic', 'Irregular'];
|
datetime_out: '',
|
||||||
const categoryOptions: TimesheetForm['category_of_work'][] = [
|
total_work_hour: 0,
|
||||||
'Cleaning',
|
remarks: '',
|
||||||
'Gardening/Pool',
|
approval: false,
|
||||||
'Maintenance',
|
};
|
||||||
'Supervision',
|
|
||||||
'Guest Service',
|
const typeOfWorkOptions: TimesheetForm['type_of_work'][] = ['Running', 'Periodic', 'Irregular'];
|
||||||
'Administration',
|
const categoryOptions: TimesheetForm['category_of_work'][] = [
|
||||||
'Non Billable'
|
'Cleaning',
|
||||||
];
|
'Gardening/Pool',
|
||||||
|
'Maintenance',
|
||||||
onMount(async () => {
|
'Supervision',
|
||||||
const { data, error } = await supabase
|
'Guest Service',
|
||||||
.from('vb_villas')
|
'Administration',
|
||||||
.select('id, villa_name, villa_status')
|
'Non Billable',
|
||||||
.eq('villa_status', 'Active');
|
];
|
||||||
|
|
||||||
if (error) {
|
onMount(async () => {
|
||||||
console.error('Failed to fetch villas:', error.message);
|
// Fetch villas
|
||||||
} else {
|
const { data: villaData, error: villaError } = await supabase
|
||||||
villas = data.map(v => ({
|
.from('vb_villas')
|
||||||
id: v.id,
|
.select('id, villa_name, villa_status')
|
||||||
name: v.villa_name
|
.eq('villa_status', 'Active');
|
||||||
}));
|
|
||||||
}
|
if (villaError) {
|
||||||
});
|
console.error('Failed to fetch villas:', villaError.message);
|
||||||
|
} else if (villaData) {
|
||||||
function calculateTotalHours() {
|
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');
|
||||||
|
|
||||||
|
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) {
|
if (form.datetime_in && form.datetime_out) {
|
||||||
const start = new Date(form.datetime_in);
|
const start = new Date(form.datetime_in);
|
||||||
const end = new Date(form.datetime_out);
|
const end = new Date(form.datetime_out);
|
||||||
const diffInMs = end.getTime() - start.getTime();
|
const diffInMs = end.getTime() - start.getTime();
|
||||||
|
const hours = diffInMs / (1000 * 60 * 60);
|
||||||
// Convert milliseconds to hours (with decimal), round to 2 decimal places
|
form.total_work_hour = Math.max(Number(hours.toFixed(2)), 0);
|
||||||
const hours = diffInMs / (1000 * 60 * 60);
|
|
||||||
form.total_work_hour = Math.max(Number(hours.toFixed(2)), 0);
|
|
||||||
} else {
|
} else {
|
||||||
form.total_work_hour = 0;
|
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]);
|
||||||
async function submitForm() {
|
|
||||||
calculateTotalHours();
|
if (error) {
|
||||||
|
alert('Failed to submit timesheet: ' + error.message);
|
||||||
const { error } = await supabase.from('vb_timesheet').insert([form]);
|
} else {
|
||||||
|
alert('Timesheet submitted successfully!');
|
||||||
if (error) {
|
form = {
|
||||||
alert('Failed to submit timesheet: ' + error.message);
|
entered_by: '',
|
||||||
} else {
|
work_description: '',
|
||||||
alert('Timesheet submitted successfully!');
|
type_of_work: 'Running',
|
||||||
form = {
|
category_of_work: 'Cleaning',
|
||||||
entered_by: '',
|
villa_id: '',
|
||||||
work_description: '',
|
datetime_in: '',
|
||||||
type_of_work: 'Running',
|
datetime_out: '',
|
||||||
category_of_work: 'Cleaning',
|
total_work_hour: 0,
|
||||||
villa_id: '',
|
remarks: '',
|
||||||
datetime_in: '',
|
approval: false,
|
||||||
datetime_out: '',
|
};
|
||||||
total_work_hour: 0,
|
|
||||||
remarks: '',
|
|
||||||
approval: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={submitForm} class="space-y-4 max-w-md mx-auto p-4">
|
<div class="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||||
<h2 class="text-xl font-bold mb-4">Timesheet Entry</h2>
|
<form
|
||||||
|
on:submit|preventDefault={submitForm}
|
||||||
<input
|
class="w-full max-w-lg bg-white p-6 rounded-2xl shadow-xl space-y-4"
|
||||||
class="w-full border p-2 rounded"
|
>
|
||||||
bind:value={form.entered_by}
|
<h2 class="text-2xl font-bold text-center mb-6">Timesheet Entry</h2>
|
||||||
placeholder="Entered by"
|
|
||||||
required
|
<div>
|
||||||
/>
|
<label for="t_eb" class="block text-sm font-medium mb-1">Entered By</label>
|
||||||
|
<select
|
||||||
<textarea
|
id="t_eb"
|
||||||
class="w-full border p-2 rounded"
|
class="w-full border p-2 rounded"
|
||||||
bind:value={form.work_description}
|
bind:value={form.entered_by}
|
||||||
placeholder="Work Description"
|
required
|
||||||
required
|
>
|
||||||
></textarea>
|
<option value="" disabled selected>Select Employee</option>
|
||||||
|
{#each employees as employee}
|
||||||
<select class="w-full border p-2 rounded" bind:value={form.type_of_work}>
|
<option value={employee.id}>{employee.name}</option>
|
||||||
{#each typeOfWorkOptions as option}
|
{/each}
|
||||||
<option value={option}>{option}</option>
|
</select>
|
||||||
{/each}
|
</div>
|
||||||
</select>
|
|
||||||
|
<div>
|
||||||
<select class="w-full border p-2 rounded" bind:value={form.category_of_work}>
|
<label for="t_wd" class="block text-sm font-medium mb-1">Work Description</label>
|
||||||
{#each categoryOptions as option}
|
<textarea
|
||||||
<option value={option}>{option}</option>
|
id="t_wd"
|
||||||
{/each}
|
class="w-full border border-gray-300 p-2 rounded"
|
||||||
</select>
|
bind:value={form.work_description}
|
||||||
|
placeholder="Describe the work"
|
||||||
<select class="w-full border p-2 rounded" bind:value={form.villa_id} required>
|
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}
|
{#each villas as villa}
|
||||||
<option value={villa.id}>{villa.name}</option>
|
<option value={villa.id}>{villa.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
</select>
|
||||||
</select>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
<label class="block">
|
<label for="tdto" class="block text-sm font-medium mb-1">Date/Time In</label>
|
||||||
<input
|
<input
|
||||||
|
id="tdto"
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
class="w-full border p-2 rounded"
|
class="w-full border p-2 rounded"
|
||||||
bind:value={form.datetime_in}
|
bind:value={form.datetime_in}
|
||||||
on:change={calculateTotalHours}
|
on:change={calculateTotalHours}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
/>
|
</div>
|
||||||
</label>
|
|
||||||
|
<div>
|
||||||
<label class="block">
|
<label for="dto" class="block text-sm font-medium mb-1">Date/Time Out</label>
|
||||||
<input
|
<input
|
||||||
|
id="dto"
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
class="w-full border p-2 rounded"
|
class="w-full border p-2 rounded"
|
||||||
bind:value={form.datetime_out}
|
bind:value={form.datetime_out}
|
||||||
on:change={calculateTotalHours}
|
on:change={calculateTotalHours}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="text-sm">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="text-sm">
|
||||||
<textarea
|
<label for="ttwo" class="block font-medium mb-1">Total Work Hours</label>
|
||||||
class="w-full border p-2 rounded"
|
<div id="ttwo" class="px-3 py-2">{form.total_work_hour}</div>
|
||||||
bind:value={form.remarks}
|
</div>
|
||||||
placeholder="Remarks"
|
|
||||||
></textarea>
|
<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
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
type="submit"
|
class="w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition"
|
||||||
>
|
>
|
||||||
Submit Timesheet
|
Submit Timesheet
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user