enhance form issue

This commit is contained in:
2025-06-18 13:28:37 +08:00
parent d1bd89c17b
commit 039c7914a0
2 changed files with 307 additions and 453 deletions

View File

@@ -59,7 +59,6 @@
{ key: "contact_pos_tertiary", title: "Tertiary Contact Position" }, { key: "contact_pos_tertiary", title: "Tertiary Contact Position" },
{ key: "contact_email_tertiary", title: "Tertiary Email" }, { key: "contact_email_tertiary", title: "Tertiary Email" },
{ key: "website", title: "Website" }, { key: "website", title: "Website" },
{ key: "created_at", title: "Created At" },
]; ];
const excludedKeys = ["id", "created_by", "created_at", "updated_at"]; const excludedKeys = ["id", "created_by", "created_at", "updated_at"];
$: formColumns = columns.filter((col) => !excludedKeys.includes(col.key)); $: formColumns = columns.filter((col) => !excludedKeys.includes(col.key));

View File

@@ -3,7 +3,6 @@
// For example, you could handle form submission here // For example, you could handle form submission here
import { onMount } from "svelte"; import { onMount } from "svelte";
import { supabase } from "$lib/supabaseClient"; import { supabase } from "$lib/supabaseClient";
import { writable } from "svelte/store";
const priority = [ const priority = [
{ label: "Low", value: "Low" }, { label: "Low", value: "Low" },
@@ -12,11 +11,16 @@
{ label: "Critical", value: "Critical" }, { label: "Critical", value: "Critical" },
]; ];
const issueSource = [ const issueSource = [
{ label: "Email", value: "Email" }, { label: "Guest Assistant", value: "Guest Assistant" },
{ label: "Phone Call", value: "Phone Call" }, { label: "Villa Attendant", value: "Villa Attendant" },
{ label: "In-Person", value: "In-Person" }, { label: "Villa Supervisor", value: "Villa Supervisor" },
{ label: "Online Form", value: "Online Form" }, { label: "Villa Manager", value: "Villa Manager" },
{ label: "Other", value: "Other" }, { label: "Operation Manager", value: "Operation Manager" },
{ label: "Quality Control Manager", value: "Quality Control Manager" },
{ label: "Owner", value: "Owner" },
{ label: "Sales/Reservations", value: "Sales/Reservations" },
{ label: "Guest", value: "Guest" },
{ label: "Agent", value: "Agent" },
]; ];
const issueTypes = [ const issueTypes = [
@@ -29,16 +33,10 @@
{ label: "Cleanliness - Floor", value: "Cleanliness - Floor" }, { label: "Cleanliness - Floor", value: "Cleanliness - Floor" },
{ label: "Cleanliness - Kitchen", value: "Cleanliness - Kitchen" }, { label: "Cleanliness - Kitchen", value: "Cleanliness - Kitchen" },
{ label: "Cleanliness - Bathroom", value: "Cleanliness - Bathroom" }, { label: "Cleanliness - Bathroom", value: "Cleanliness - Bathroom" },
{ { label: "Maintenance - Electrical", value: "Maintenance - Electrical",},
label: "Maintenance - Electrical",
value: "Maintenance - Electrical",
},
{ label: "Maintenance - Plumbing", value: "Maintenance - Plumbing" }, { label: "Maintenance - Plumbing", value: "Maintenance - Plumbing" },
{ label: "Maintenance - HVAC", value: "Maintenance - HVAC" }, { label: "Maintenance - HVAC", value: "Maintenance - HVAC" },
{ { label: "Maintenance - Structural", value: "Maintenance - Structural" },
label: "Maintenance - Structural",
value: "Maintenance - Structural",
},
{ label: "Safety Issue", value: "Safety Issue" }, { label: "Safety Issue", value: "Safety Issue" },
{ label: "Security Concern", value: "Security Concern" }, { label: "Security Concern", value: "Security Concern" },
{ label: "Other", value: "Other" }, { label: "Other", value: "Other" },
@@ -72,231 +70,169 @@
{ label: "Bedroom 1", value: "Bedroom 1" }, { label: "Bedroom 1", value: "Bedroom 1" },
{ label: "Bedroom 2", value: "Bedroom 2" }, { label: "Bedroom 2", value: "Bedroom 2" },
{ label: "Bedroom 3", value: "Bedroom 3" }, { label: "Bedroom 3", value: "Bedroom 3" },
{ label: "Ceiling", value: "Ceiling" }, { label: "Kids Room", value: "Kids Room" },
{ label: "Dining Area", value: "Dining Area" },
{ label: "Door", value: "Door" },
{ label: "Entrance", value: "Entrance" },
{ label: "Garden", value: "Garden" }, { label: "Garden", value: "Garden" },
{ label: "General", value: "General" },
{ label: "Glass", value: "Glass" },
{ label: "Hallway", value: "Hallway" },
{ label: "Kitchen", value: "Kitchen" }, { label: "Kitchen", value: "Kitchen" },
{ label: "Laundry Area", value: "Laundry Area" }, { label: "Pump Room", value: "Pump Room" },
{ label: "Living Room", value: "Living Room" }, { label: "Living Room", value: "Living Room" },
{ label: "Outdoor Area", value: "Outdoor Area" }, { label: "Outdoor Area", value: "Outdoor Area" },
{ label: "Parking Area", value: "Parking Area" }, { label: "Parking Area", value: "Parking Area" },
{ label: "Pool Area", value: "Pool Area" }, { label: "Pool", value: "Pool" },
{ label: "Roof", value: "Roof" }, { label: "Roof", value: "Roof" },
{ label: "Stairs", value: "Stairs" }, { label: "Stairs", value: "Stairs" },
{ label: "Storage", value: "Storage" }, { label: "Storage", value: "Storage" },
{ label: "Terrace", value: "Terrace" }, { label: "Staff Room", value: "Staff Room" },
{ label: "Toilet", value: "Toilet" }, { label: "Transport", value: "Transport" },
{ label: "Wall", value: "Wall" },
{ label: "Window", value: "Window" }, { label: "Window", value: "Window" },
{ label: "Others", value: "Others" }, { label: "Others", value: "Others" },
]; ];
//InputBy & reportedBy should dropdown data from active employee, from table vb_employee (field employee_name and employee_status = 'Active')
const inputBy = [
{ label: "Admin", value: "Admin" },
{ label: "Staff", value: "Staff" },
{ label: "Manager", value: "Manager" },
{ label: "Guest", value: "Guest" },
];
const followUp = [ const followUp = [
{ label: "Yes", value: "true" }, { label: "Communication Needed by Reservation", value: "true" },
{ label: "No", value: "false" }, { label: "Communication Completed", value: "false" },
];
const reportedBy = [
{ label: "Admin", value: "Admin" },
{ label: "Staff", value: "Staff" },
{ label: "Manager", value: "Manager" },
{ label: "Guest", value: "Guest" },
]; ];
let dataUser: { id: string; employee_name: string }[] = [];
let dataVilla: { id: string; villa_name: string }[] = [];
let issueImageFile: File | null = null; let issueImageFile: File | null = null;
let issueImageUrl: string = ""; let issueImageUrl: string = "";
let isSubmitting = false;
function handleFileChange(event: Event): void { function handleFileChange(event: Event): void {
const target = event.target as HTMLInputElement; const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) { if (target.files && target.files.length > 0) {
const file = target.files[0]; const file = target.files[0];
if (!["image/jpeg", "image/png", "image/webp"].includes(file.type)) {
alert("Only JPG, PNG, or WEBP images are allowed.");
return;
}
if (file.size > 2 * 1024 * 1024) {
alert("Image must be less than 2MB.");
return;
}
issueImageFile = file; issueImageFile = file;
issueImageUrl = URL.createObjectURL(file); issueImageUrl = URL.createObjectURL(file);
} }
} }
type Villa = {
id: string;
villa_name: string;
};
type User = {
id: string;
employee_name: string;
};
let dataVilla: Villa[] = [];
let dataUser: User[] = [];
onMount(async () => { onMount(async () => {
const { data, error } = await supabase // Fetch active villas
const { data: villaData, error: villaError } = await supabase
.from("vb_villas") .from("vb_villas")
.select("id, villa_name"); .select("id, villa_name")
.eq("villa_status", "Active");
if (error) { if (villaError) {
console.error("Error fetching villas:", error); console.error("Error fetching villas:", villaError);
} else if (data) { } else {
dataVilla = data; dataVilla = villaData || [];
} }
// Fetch active employees
const { data: userData, error: userError } = await supabase const { data: userData, error: userError } = await supabase
.from("vb_employee") .from("vb_employee")
.select("id, employee_name"); .select("id, employee_name")
.eq("employee_status", "Active");
if (userError) { if (userError) {
console.error("Error fetching users:", userError); console.error("Error fetching employees:", userError);
} else if (userData) { } else {
dataUser = userData; dataUser = userData || [];
} }
}); });
type Issue = {
name: string;
villa_name: string;
area_of_villa: string;
priority: string;
issue_type: string;
issue_number: string;
move_issue: boolean;
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;
guest_has_aggreed_issue_has_been_resolved: boolean;
follow_up: boolean;
need_approval: boolean;
created_at: string;
};
type issueInsert = {
name: string;
villa_id: string;
area_of_villa: string;
priority: string;
issue_type: string;
issue_number: string;
move_issue: boolean;
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;
guest_has_aggreed_issue_has_been_resolved: boolean;
follow_up: boolean;
need_approval: boolean;
created_at: string;
};
async function handleSubmit(event: Event): Promise<void> { async function handleSubmit(event: Event): Promise<void> {
event.preventDefault(); event.preventDefault();
isSubmitting = true;
try {
const formData = new FormData(event.target as HTMLFormElement); const formData = new FormData(event.target as HTMLFormElement);
// Validate form data
if (!validateForm(formData)) {
console.error("Form validation failed");
return;
}
// Upload issue image if provided
if (issueImageFile) { if (issueImageFile) {
const filePath = `issues/${Date.now()}_${issueImageFile.name}`;
const { data, error } = await supabase.storage const { data, error } = await supabase.storage
.from("villabugis") .from("villabugis")
.upload(`issues/${issueImageFile.name}`, issueImageFile); .upload(filePath, issueImageFile);
if (error) { if (error) {
console.error("Error uploading image:", error); alert("Image upload failed");
console.error(error);
return; return;
} }
issueImageUrl = data.path; // Assuming data.Key contains the URL or path to the uploaded image const { data: publicData } = supabase.storage
.from("villabugis")
.getPublicUrl(filePath);
issueImageUrl = publicData.publicUrl;
} }
const issue: issueInsert = { const issue_number = await generateIssueNumber();
name: formData.get("name") as string,
const issue = {
issue_number,
name: formData.get("description_of_the_issue") as string,
villa_id: formData.get("villa_name") as string, villa_id: formData.get("villa_name") as string,
area_of_villa: formData.get("area_of_villa") as string, area_of_villa: formData.get("area_of_villa") as string,
priority: formData.get("priority") as string, priority: formData.get("priority") as string,
issue_type: formData.get("issue_type") as string, issue_type: formData.get("issue_type") as string,
issue_number: formData.get("issue_number") as string,
move_issue: formData.get("move_issue") === "false",
description_of_the_issue: formData.get(
"description_of_the_issue",
) as string,
reported_date: formData.get("reported_date") as string,
issue_related_image: issueImageUrl,
issue_source: formData.get("issue_source") as string, issue_source: formData.get("issue_source") as string,
reported_date: formData.get("reported_date") as string,
reported_by: formData.get("reported_by") as string, reported_by: formData.get("reported_by") as string,
input_by: formData.get("input_by") as string, input_by: formData.get("input_by") as string,
resolution: formData.get("resloution") as string,
guest_has_aggreed_issue_has_been_resolved: formData.get("guest_has_aggreed_issue_has_been_resolved") as string,
follow_up: formData.get("follow_up") as string,
guest_communication: formData.get("guest_communication") as string, guest_communication: formData.get("guest_communication") as string,
resolution: formData.get("resolution") as string, issue_related_image: issueImageUrl,
guest_has_aggreed_issue_has_been_resolved: url_drive: formData.get("url_drive") as string,
formData.get("guest_has_aggreed_issue_has_been_resolved") === created_at: new Date().toISOString()
"false",
follow_up: formData.get("follow_up") === "true" ? true : false,
need_approval: false, // Set this based on your logic
created_at: new Date().toISOString(),
}; };
const { data, error } = await supabase // Insert into Supabase
.from("vb_issues") const { error } = await supabase.from("vb_issues").insert([issue]);
.insert([issue]);
if (error) { if (error) {
console.error("Error submitting issue:", error); alert("Error submitting issue");
} else { console.error(error);
console.log("Issue submitted successfully:", data); return;
alert("Issue submitted successfully!");
}
} }
export let formErrors = writable<{ [key: string]: string }>({}); // POST to webhook
await fetch("https://flow.catalis.app/webhook/vb-issuecreate", {
function validateForm(formData: FormData): boolean { method: "POST",
const errors: { [key: string]: string } = {}; headers: { "Content-Type": "application/json" },
const requiredFields = [ body: JSON.stringify(issue)
"description_of_the_issue",
"issue_source",
"villa_name",
"reported_date",
"reported_by",
"priority",
"issue_type",
"due_issue_date",
"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); alert("Issue submitted successfully!");
return Object.keys(errors).length === 0; (event.target as HTMLFormElement).reset();
issueImageUrl = "";
issueImageFile = null;
} catch (err) {
alert("Unexpected error");
console.error(err);
} finally {
isSubmitting = false;
}
} }
function errorClass(field: string): string { async function generateIssueNumber(): Promise<string> {
return $formErrors[field] ? "border-red-500" : "border"; const { data } = await supabase
.from("vb_issues")
.select("issue_number")
.order("created_at", { ascending: false })
.limit(1);
if (!data || data.length === 0) {
return "ISS-000500";
}
const last = data[0].issue_number?.split("-")[1] || "500";
const nextNumber = parseInt(last, 10) + 1;
return `ISS-${nextNumber.toString().padStart(6, "0")}`;
} }
</script> </script>
@@ -323,76 +259,46 @@
<!-- Left Column --> <!-- Left Column -->
<div class="space-y-5"> <div class="space-y-5">
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Description of Issues<span class="text-red-500">*</span Description of Issues<span class="text-red-500">*</span>
></label
>
<input <input
name="description_of_the_issue" name="description_of_the_issue"
type="text" type="text"
placeholder="Tell detail of the issue" placeholder="Tell detail of the issue"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400"
'description_of_the_issue', required
)}"
/> />
{#if $formErrors.description_of_the_issue} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.description_of_the_issue}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">Issue Source
>Issue Source<span class="text-red-500">*</span></label <span class="text-red-500">*</span>
> <select name="issue_source" class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600" required>
<select <option value="" disabled selected>Select option...</option>
name="issue_source"
class={`w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600 ${errorClass("issue_source")}`}
>
<option value="" disabled selected
>Select option...</option
>
{#each issueSource as source} {#each issueSource as source}
<option value={source.value}>{source.label}</option> <option value={source.value}>{source.label}</option>
{/each} {/each}
</select> </select>
{#if $formErrors.issue_source} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.issue_source}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Villa Name<span class="text-red-500">*</span></label Villa Name
> <span class="text-red-500">*</span>
<select <select
name="villa_name" name="villa_name"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600" required>
'villa_name', <option value="" disabled selected>Select option...</option>
)}"
>
<option value="" disabled selected
>Select option...</option
>
{#each dataVilla as villa} {#each dataVilla as villa}
<option value={villa.id}>{villa.villa_name}</option> <option value={villa.id}>{villa.villa_name}</option>
{/each} {/each}
</select> </select>
{#if $formErrors.villa_name} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.villa_name}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Issue related image</label Issue related image
> <div class="w-full border-2 border-dashed rounded-xl px-4 py-10 text-center text-gray-400 cursor-pointer hover:bg-gray-50 transition relative">
<div
class="w-full border-2 border-dashed rounded-xl px-4 py-10 text-center text-gray-400 cursor-pointer hover:bg-gray-50 transition relative"
>
<input <input
name="issue_related_image" name="issue_related_image"
type="file" type="file"
@@ -410,7 +316,6 @@
Supported formats: JPG, PNG, GIF Supported formats: JPG, PNG, GIF
</p> </p>
</div> </div>
{#if issueImageUrl} {#if issueImageUrl}
<div class="mt-4"> <div class="mt-4">
<p class="text-sm text-gray-600 mb-2">Preview:</p> <p class="text-sm text-gray-600 mb-2">Preview:</p>
@@ -421,171 +326,126 @@
/> />
</div> </div>
{/if} {/if}
</label>
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Date Reported<span class="text-red-500">*</span></label Date Reported
> <span class="text-red-500">*</span>
<input <input
name="reported_date" name="reported_date"
type="date" type="date"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400"
'reported_date', required
)}"
/> />
{#if $formErrors.reported_date} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.reported_date}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Reported By<span class="text-red-500">*</span></label Reported By
> <span class="text-red-500">*</span>
<select <select
name="reported_by" name="reported_by"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 text-gray-600 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 text-gray-600"
'reported_by', required
)}"
>
<option value="" disabled selected
>Select option...</option
> >
<option value="" disabled selected>Select option...</option>
{#each dataUser as reporter} {#each dataUser as reporter}
<option value={reporter.id}> <option value={reporter.id}>
{reporter.employee_name} {reporter.employee_name}
</option> </option>
{/each} {/each}
</select> </select>
{#if $formErrors.reported_by} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.reported_by}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">URLDrive
>URLDrive</label
>
<input <input
name="url_drive" name="url_drive"
type="url" type="url"
placeholder="Enter URL" placeholder="Enter URL"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400" class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400"
/> />
</label>
</div> </div>
</div> </div>
<!-- Right Column --> <!-- Right Column -->
<div class="space-y-5"> <div class="space-y-5">
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Priority<span class="text-red-500">*</span></label Priority
> <span class="text-red-500">*</span>
<select <select
name="priority" name="priority"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600"
'priority', required
)}"
>
<option value="" disabled selected
>Select option...</option
> >
<option value="" disabled selected>Select option...</option>
{#each priority as p} {#each priority as p}
<option value={p.value}>{p.label}</option> <option value={p.value}>{p.label}</option>
{/each} {/each}
</select> </select>
{#if $formErrors.priority} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.priority}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Issue Type<span class="text-red-500">*</span></label Issue Type<span class="text-red-500">*</span>
>
<select <select
name="issue_type" name="issue_type"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600"
'issue_type', required
)}"
>
<option value="" disabled selected
>Select option...</option
> >
<option value="" disabled selected>Select option...</option>
{#each issueTypes as type} {#each issueTypes as type}
<option value={type.value}>{type.label}</option> <option value={type.value}>{type.label}</option>
{/each} {/each}
</select> </select>
{#if $formErrors.issue_type} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.issue_type}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Area of Villa<span class="text-red-500">*</span></label Area of Villa<span class="text-red-500">*</span>
>
<select <select
name="area_of_villa" name="area_of_villa"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600"
'area_of_villa', required
)}"
>
<option value="" disabled selected
>Select option...</option
> >
<option value="" disabled selected>Select option...</option>
{#each areaOfVilla as area} {#each areaOfVilla as area}
<option value={area.value}>{area.label}</option> <option value={area.value}>{area.label}</option>
{/each} {/each}
</select> </select>
{#if $formErrors.area_of_villa} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.area_of_villa}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Customer / Guest Name</label Customer / Guest Name
>
<input <input
name="name" name="name"
type="text" type="text"
placeholder="Enter text" placeholder="Enter text"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400" class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400"
/> />
</label>
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Due Issue Date<span class="text-red-500">*</span Due Issue Date<span class="text-red-500">*</span>
></label
>
<input <input
name="due_issue_date" name="due_issue_date"
type="date" type="date"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400"
'due_issue_date',
)}"
/> />
{#if $formErrors.due_issue_date} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.due_issue_date}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Input By<span class="text-red-500">*</span></label Input By<span class="text-red-500">*</span>
>
<select <select
name="input_by" name="input_by"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600 {errorClass( class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600"
'input_by', required
)}"
> >
<option value="" disabled selected <option value="" disabled selected
>Select option...</option >Select option...</option
@@ -594,16 +454,11 @@
<option value={input.id}>{input.employee_name}</option> <option value={input.id}>{input.employee_name}</option>
{/each} {/each}
</select> </select>
{#if $formErrors.input_by} </label>
<p class="text-sm text-red-500 mt-1">
{$formErrors.input_by}
</p>
{/if}
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Follow Up</label Follow Up
>
<select <select
name="follow_up" name="follow_up"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600" class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400 text-gray-600"
@@ -615,6 +470,7 @@
<option value={follow.value}>{follow.label}</option> <option value={follow.value}>{follow.label}</option>
{/each} {/each}
</select> </select>
</label>
</div> </div>
</div> </div>
</div> </div>
@@ -622,38 +478,36 @@
<!-- Full Width Fields --> <!-- Full Width Fields -->
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Resolved How?</label Resolved How?
>
<textarea <textarea
name="resolution" name="resolution"
rows="3" rows="3"
placeholder="How you resolve? e.g. 'copy to project'" placeholder="How you resolve? e.g. 'copy to project'"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400" class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400"
></textarea> ></textarea>
</label>
</div> </div>
<div> <div>
<label class="block text-sm font-medium mb-1" <label class="block text-sm font-medium mb-1">
>Guest Communication</label Guest Communication
>
<textarea <textarea
name="guest_communication" name="guest_communication"
rows="3" rows="3"
placeholder="Communication with guest while still in the villa" placeholder="Communication with guest while still in the villa"
class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400" class="w-full border rounded-xl px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-400"
></textarea> ></textarea>
</label>
</div> </div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<label class="text-sm">Guest has agreed issue has been resolved
<input <input
name="guest_has_aggreed_issue_has_been_resolved" name="guest_has_aggreed_issue_has_been_resolved"
value="false" value="false"
type="checkbox" type="checkbox"
id="guest_agreed"
class="h-4 w-4 rounded border-gray-300" class="h-4 w-4 rounded border-gray-300"
/> />
<label for="guest_agreed" class="text-sm" </label>
>Guest has agreed issue has been resolved</label
>
</div> </div>
</div> </div>
@@ -662,8 +516,9 @@
<button <button
type="submit" type="submit"
class="bg-purple-600 text-white px-8 py-3 rounded-xl hover:bg-purple-700 transition-all font-medium shadow-md" class="bg-purple-600 text-white px-8 py-3 rounded-xl hover:bg-purple-700 transition-all font-medium shadow-md"
disabled={isSubmitting}
> >
Submit {isSubmitting ? "Submitting..." : "Submit"}
</button> </button>
</div> </div>
</form> </form>