first commit

This commit is contained in:
Aji Setiaji
2025-05-27 21:43:01 +07:00
commit 8d984635af
30 changed files with 7314 additions and 0 deletions

View File

@@ -0,0 +1,986 @@
<script lang="ts">
import { supabase } from "$lib/supabaseClient";
import { onMount } from "svelte";
import { writable } from "svelte/store";
const priority = [
{ label: "Low", value: "Low" },
{ label: "Medium", value: "Medium" },
{ label: "High", value: "High" },
{ label: "Critical", value: "Critical" },
];
const issueSource = [
{ label: "Email", value: "Email" },
{ label: "Phone Call", value: "Phone Call" },
{ label: "In-Person", value: "In-Person" },
{ label: "Online Form", value: "Online Form" },
{ label: "Other", value: "Other" },
];
const issueTypes = [
{ label: "Facilities - Light", value: "Facilities - Light" },
{ label: "Facilities - Linen", value: "Facilities - Linen" },
{ label: "Facilities - Other", value: "Facilities - Other" },
{ label: "Facilities - Towel", value: "Facilities - Towel" },
{ label: "Facilities - Fan", value: "Facilities - Fan" },
{ label: "Cleanliness - Other", value: "Cleanliness - Other" },
{ label: "Cleanliness - Floor", value: "Cleanliness - Floor" },
{ label: "Cleanliness - Kitchen", value: "Cleanliness - Kitchen" },
{ label: "Cleanliness - Bathroom", value: "Cleanliness - Bathroom" },
{
label: "Maintenance - Electrical",
value: "Maintenance - Electrical",
},
{ label: "Maintenance - Plumbing", value: "Maintenance - Plumbing" },
{ label: "Maintenance - HVAC", value: "Maintenance - HVAC" },
{
label: "Maintenance - Structural",
value: "Maintenance - Structural",
},
{ label: "Safety Issue", value: "Safety Issue" },
{ label: "Security Concern", value: "Security Concern" },
{ label: "Other", value: "Other" },
{ label: "General Inquiry", value: "General Inquiry" },
{ label: "Feedback", value: "Feedback" },
{ label: "Complaint", value: "Complaint" },
{ label: "Request", value: "Request" },
{ label: "Suggestion", value: "Suggestion" },
{ label: "Booking Issue", value: "Booking Issue" },
{ label: "Payment Issue", value: "Payment Issue" },
{ label: "Cancellation Request", value: "Cancellation Request" },
{ label: "Refund Request", value: "Refund Request" },
{ label: "Reservation Change", value: "Reservation Change" },
{ label: "Check-in Issue", value: "Check-in Issue" },
{ label: "Check-out Issue", value: "Check-out Issue" },
];
const areaOfVilla = [
{ label: "All Bathrooms", value: "All Bathrooms" },
{ label: "All Guest Houses", value: "All Guest Houses" },
{ label: "All Rooms", value: "All Rooms" },
{ label: "All Villa Areas", value: "All Villa Areas" },
{ label: "Balcony", value: "Balcony" },
{ label: "Bathroom (Guest)", value: "Bathroom (Guest)" },
{ label: "Bathroom (Master)", value: "Bathroom (Master)" },
{ label: "Bathroom 1", value: "Bathroom 1" },
{ label: "Bathroom 2", value: "Bathroom 2" },
{ label: "Bathroom 3", value: "Bathroom 3" },
{ label: "Bedroom (Guest)", value: "Bedroom (Guest)" },
{ label: "Bedroom (Master)", value: "Bedroom (Master)" },
{ label: "Bedroom 1", value: "Bedroom 1" },
{ label: "Bedroom 2", value: "Bedroom 2" },
{ label: "Bedroom 3", value: "Bedroom 3" },
{ label: "Ceiling", value: "Ceiling" },
{ label: "Dining Area", value: "Dining Area" },
{ label: "Door", value: "Door" },
{ label: "Entrance", value: "Entrance" },
{ label: "Garden", value: "Garden" },
{ label: "General", value: "General" },
{ label: "Glass", value: "Glass" },
{ label: "Hallway", value: "Hallway" },
{ label: "Kitchen", value: "Kitchen" },
{ label: "Laundry Area", value: "Laundry Area" },
{ label: "Living Room", value: "Living Room" },
{ label: "Outdoor Area", value: "Outdoor Area" },
{ label: "Parking Area", value: "Parking Area" },
{ label: "Pool Area", value: "Pool Area" },
{ label: "Roof", value: "Roof" },
{ label: "Stairs", value: "Stairs" },
{ label: "Storage", value: "Storage" },
{ label: "Terrace", value: "Terrace" },
{ label: "Toilet", value: "Toilet" },
{ label: "Wall", value: "Wall" },
{ label: "Window", value: "Window" },
{ label: "Others", value: "Others" },
];
const inputBy = [
{ label: "Admin", value: "Admin" },
{ label: "Staff", value: "Staff" },
{ label: "Manager", value: "Manager" },
{ label: "Guest", value: "Guest" },
];
const reportedBy = [
{ label: "Admin", value: "Admin" },
{ label: "Staff", value: "Staff" },
{ label: "Manager", value: "Manager" },
{ label: "Guest", value: "Guest" },
];
type Villa = {
id: string;
name: string;
};
let dataVilla: Villa[] = [];
onMount(async () => {
const { data, error } = await supabase
.from("villas")
.select("id, name");
if (error) {
console.error("Error fetching villas:", error);
} else if (data) {
dataVilla = data;
}
});
type Issue = {
id: number;
name: string;
villa_name: string;
area_of_villa: string;
priority: string;
issue_type: string;
issue_number: string;
move_issue: string;
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_name: string;
area_of_villa: string;
priority: string;
issue_type: string;
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;
};
let allRows: Issue[] = [];
type columns = {
key: string;
title: string;
};
const columns: columns[] = [
{ key: "name", title: "Name" },
{ key: "villa_name", title: "Villa Name" },
{ key: "area_of_villa", title: "Area Of Villa" },
{ key: "priority", title: "Priority" },
{ key: "issue_type", title: "Issue Type" },
{ key: "issue_number", title: "Issue Number" },
{ key: "move_issue", title: "Move Issue" },
{ key: "description_of_the_issue", title: "Description of The Issue" },
{ key: "reported_date", title: "Reported Date" },
{ key: "issue_related_image", title: "Issue Related Image" },
{ key: "issue_source", title: "Issue Source" },
{ key: "reported_by", title: "Reported By" },
{ key: "input_by", title: "Input By" },
{ key: "guest_communication", title: "Guest Communication" },
{ key: "resolution", title: "Resolution" },
{
key: "guest_has_aggreed_issue_has_been_resolved",
title: "Guest Has Aggred Issue Has Been Resolved",
},
{ key: "follow_up", title: "Follow Up" },
{ key: "need_approval", title: "Need Approval" },
{ key: "created_at", title: "Created At" },
{ key: "actions", title: "Actions" },
];
async function fetchIssues() {
const { data: issues, error: issueError } = await supabase
.from("issues")
.select("*")
.order("created_at", { ascending: false });
if (issueError) {
console.error("Error fetching issues:", issueError);
return;
}
// Ambil semua villa_id unik dari issues
const villaIds = [...new Set(issues.map((i: Issue) => i.villa_name))];
const { data: villas, error: villaError } = await supabase
.from("villas")
.select("*")
.in("id", villaIds);
if (villaError) {
console.error("Error fetching villas:", villaError);
return;
}
// Gabungkan data villa ke dalam setiap issue
allRows = issues.map((issue: Issue) => ({
...issue,
villa_name:
villas.find((v) => v.id === issue.villa_name).name || null,
}));
}
let currentPage = 1;
let rowsPerPage = 5;
$: totalPages = Math.ceil(allRows.length / rowsPerPage);
$: paginatedRows = allRows.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage,
);
function editIssue(id: number) {
alert(`Edit issue with ID ${id}`);
}
function goToPage(page: number) {
if (page >= 1 && page <= totalPages) currentPage = page;
}
onMount(() => {
fetchIssues();
});
// Initialize the first page
$: currentPage = 1;
let showModal = false;
let isEditing = false;
let currentEditingId: string | null = null;
let newIssue: Record<string, any> = {};
const excludedKeys = [
"id",
"created_at",
"move_issue",
"issue_number",
"actions",
];
const formColumns = columns.filter(
(col) => !excludedKeys.includes(col.key),
);
function openModal(issue?: Record<string, any>) {
if (issue) {
isEditing = true;
currentEditingId = issue.id;
newIssue = { ...issue };
} else {
isEditing = false;
currentEditingId = null;
newIssue = {};
}
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("issues")
.update(newIssue)
.eq("id", currentEditingId);
if (error) {
alert("Error updating issue: " + error.message);
console.error("Error updating issue:", error);
return;
}
} else {
const issueInsert: issueInsert = {
name: formData.get("name") as string,
villa_name: formData.get("villa_name") as string,
area_of_villa: formData.get("area_of_villa") as string,
priority: formData.get("priority") as string,
issue_type: formData.get("issue_type") as string,
description_of_the_issue: formData.get(
"description_of_the_issue",
) as string,
reported_date: formData.get("reported_date") as string,
issue_related_image: imagePreviewUrl || "",
issue_source: formData.get("issue_source") as string,
reported_by: formData.get("reported_by") as string,
input_by: formData.get("input_by") as string,
guest_communication: formData.get(
"guest_communication",
) as string,
resolution: formData.get("resolution") as string,
guest_has_aggreed_issue_has_been_resolved:
formData.get(
"guest_has_aggreed_issue_has_been_resolved",
) === "true"
? true
: false,
follow_up: formData.get("follow_up") === "true" ? true : false,
need_approval:
formData.get("need_approval") === "true" ? true : false,
};
const { error } = await supabase
.from("issues")
.insert([issueInsert]);
if (error) {
console.error("Error adding issue:", error);
return;
}
}
await fetchIssues();
showModal = false;
}
async function deleteIssue(id: number) {
if (confirm("Are you sure you want to delete this issue?")) {
const { error } = await supabase
.from("issues")
.delete()
.eq("id", id);
if (error) {
console.error("Error deleting issue:", error);
return;
}
await fetchIssues();
}
}
let selectedFile: File | null = null;
let imagePreviewUrl: string | null = null;
function handleFileChange(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
selectedFile = input.files[0];
imagePreviewUrl = URL.createObjectURL(selectedFile);
}
}
export let formErrors = writable<{ [key: string]: string }>({});
function validateForm(formData: FormData): boolean {
const errors: { [key: string]: string } = {};
const requiredFields = [
"name",
"description_of_the_issue",
"issue_source",
"villa_name",
"reported_date",
"reported_by",
"priority",
"issue_type",
"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);
return Object.keys(errors).length === 0;
}
function errorClass(field: string): string {
return $formErrors[field] ? "border-red-500" : "border";
}
// insert id issue to project
async function moveIssueToProject(issueId: number) {
// update move_issue field in the issue
const { error: updateError } = await supabase
.from("issues")
.update({ move_issue: "PROJECT" })
.eq("id", issueId);
if (updateError) {
console.error("Error updating issue move_issue:", updateError);
return;
}
const { error } = await supabase
.from("projects")
.insert({ issue_id: issueId });
if (error) {
console.error("Error moving issue to project:", error);
return;
}
alert(`Issue ${issueId} moved to project successfully.`);
await fetchIssues();
}
// insert id issue to purchase order
async function moveIssueToPurchaseOrder(issueId: number) {
// update move_issue field in the issue
const { error: updateError } = await supabase
.from("issues")
.update({ move_issue: "PURCHASE_ORDER" })
.eq("id", issueId);
if (updateError) {
console.error("Error updating issue move_issue:", updateError);
return;
}
const { error } = await supabase
.from("purchase_orders")
.insert({ issue_id: issueId });
if (error) {
console.error("Error moving issue to purchase order:", error);
return;
}
alert(`Issue ${issueId} moved to purchase order successfully.`);
await fetchIssues();
}
</script>
<div>
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<div>
<h2 class="text-lg font-semibold text-gray-800">Issue List</h2>
<p class="text-sm text-gray-600">
Manage and view all issues reported in the system.
</p>
</div>
<button
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm"
on:click={() => openModal()}
>
Add Issue
</button>
</div>
<div class="overflow-x-auto rounded-lg shadow mb-4">
<table class="min-w-[1000px] divide-y divide-gray-200 text-sm w-max">
<thead class="bg-gray-100">
<tr>
{#each columns as col}
{#if col.key === "name"}
<th
class="sticky left-0 px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
style="background-color: #f0f8ff; z-index: 10;"
>
{col.title}
</th>
{:else}
<th
class="px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
>
{col.title}
</th>
{/if}
{/each}
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each paginatedRows as row}
<tr class="hover:bg-gray-50 transition">
{#each columns as col}
{#if col.key === "name"}
<td
class="sticky left-0 px-4 py-2 font-medium text-blue-600"
style="background-color: #f0f8ff; cursor: pointer;"
>
{row[col.key]}
</td>
{:else if col.key === "actions"}
<td class="px-4 py-2">
<button
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-blue-700"
on:click={() => openModal(row)}
>
✏️ Edit
</button>
<button
class="inline-flex items-center gap-1 rounded bg-red-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-red-700"
on:click={() => deleteIssue(row.id)}
>
🗑️ Delete
</button>
</td>
{:else if col.key === "move_issue"}
{#if row[col.key as keyof Issue] === "PROJECT"}
<td class="px-4 py-2">
<button
class="inline-flex items-center gap-1 rounded bg-green-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-green-700 disabled:opacity-90"
disabled
>
➡️ PROJECT
</button>
</td>
{:else if row[col.key as keyof Issue] === "PURCHASE_ORDER"}
<td class="px-4 py-2">
<button
class="inline-flex items-center gap-1 rounded bg-yellow-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-yellow-700 disabled:opacity-90"
disabled
>
➡️ PURCHASE ORDER
</button>
</td>
{:else}
<td class="px-4 py-2">
<button
class="inline-flex items-center gap-1 rounded bg-green-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-green-700"
on:click={() =>
moveIssueToProject(row.id)}
>
➡️ PROJECT
</button>
<button
class="inline-flex items-center gap-1 rounded bg-yellow-600 px-3 py-1.5 text-white text-xs font-medium hover:bg-yellow-700"
on:click={() =>
moveIssueToPurchaseOrder(
row.id,
)}
>
➡️ PURCHASE ORDER
</button>
</td>
{/if}
{:else if col.key === "guest_has_aggreed_issue_has_been_resolved"}
<td class="px-4 py-2">
{#if row[col.key as keyof Issue]}
{:else}
{/if}
</td>
{:else if col.key === "need_approval"}
<td class="px-4 py-2">
{#if row[col.key as keyof Issue]}
{:else}
{/if}
</td>
{:else if col.key === "follow_up"}
<td class="px-4 py-2">
{#if row[col.key as keyof Issue]}
{:else}
{/if}
</td>
{:else}
<td class="px-4 py-2 text-gray-700"
>{row[col.key as keyof Issue]}</td
>
{/if}
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
<!-- 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>
</div>
<!-- Modal -->
{#if showModal}
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<form
on:submit|preventDefault={saveIssue}
class="bg-white p-6 rounded shadow-lg w-[600px] max-h-[90vh] overflow-y-auto space-y-4"
>
<h3 class="text-lg font-semibold">
{isEditing ? "Edit Issue" : "Add New Issue"}
</h3>
{#each formColumns as col}
{#if col.key === "name"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Customer / Guest Name</label
>
<input
class="w-full border px-3 py-2 rounded {errorClass(
'name',
)}"
name="name"
type="text"
bind:value={newIssue[col.key as keyof Issue]}
placeholder={col.title}
required
/>
{#if $formErrors.name}
<p class="text-red-500 text-xs">
{$formErrors.name}
</p>
{/if}
</div>
{:else if col.key === "guest_has_aggreed_issue_has_been_resolved"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Guest Has Aggred Issue Has Been Resolved</label
>
<select
name="guest_has_aggreed_issue_has_been_resolved"
class="w-full border px-3 py-2 rounded"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
{:else if col.key === "follow_up"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Follow Up</label
>
<select
name="follow_up"
class="w-full border px-3 py-2 rounded"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
{:else if col.key === "issue_related_image"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">
Issue Related Image
</label>
<input
name="issue_related_image"
class="w-full border px-3 py-2 rounded"
type="file"
accept="image/*"
on:change={handleFileChange}
/>
<p class="text-xs text-gray-500">
Upload an image related to the issue.
</p>
{#if imagePreviewUrl}
<img
src={imagePreviewUrl}
alt="Preview"
class="mt-2 max-h-48 rounded border"
/>
{/if}
</div>
{:else if col.key === "reported_date"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Reported Date</label
>
<input
name="reported_date"
class="w-full border px-3 py-2 rounded {errorClass(
'reported_date',
)}"
type="date"
bind:value={newIssue[col.key as keyof Issue]}
/>
{#if $formErrors.reported_date}
<p class="text-red-500 text-xs">
{$formErrors.reported_date}
</p>
{/if}
</div>
{:else if col.key === "need_approval"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Need Approval</label
>
<select
name="need_approval"
class="w-full border px-3 py-2 rounded"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
{:else if col.key === "issue_source"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Issue Source</label
>
<select
name="issue_source"
class="w-full border px-3 py-2 rounded {errorClass(
'issue_source',
)}"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="" disabled selected
>Select Source</option
>
{#each issueSource as source}
<option value={source.value}
>{source.label}</option
>
{/each}
</select>
{#if $formErrors.issue_source}
<p class="text-red-500 text-xs">
{$formErrors.issue_source}
</p>
{/if}
</div>
{:else if col.key === "reported_by"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Reported By</label
>
<select
name="reported_by"
class="w-full border px-3 py-2 rounded {errorClass(
'reported_by',
)}"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="" disabled selected
>Select Reporter</option
>
{#each reportedBy as reporter}
<option value={reporter.value}
>{reporter.label}</option
>
{/each}
</select>
{#if $formErrors.reported_by}
<p class="text-red-500 text-xs">
{$formErrors.reported_by}
</p>
{/if}
</div>
{:else if col.key === "input_by"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Input By</label
>
<select
name="input_by"
class="w-full border px-3 py-2 rounded {errorClass(
'input_by',
)}"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="" disabled selected
>Select Input By</option
>
{#each inputBy as input}
<option value={input.value}
>{input.label}</option
>
{/each}
</select>
{#if $formErrors.input_by}
<p class="text-red-500 text-xs">
{$formErrors.input_by}
</p>
{/if}
</div>
{:else if col.key === "area_of_villa"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Area of Villa</label
>
<select
name="area_of_villa"
class="w-full border px-3 py-2 rounded {errorClass(
'area_of_villa',
)}"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="" disabled selected
>Select Area</option
>
{#each areaOfVilla as area}
<option value={area.value}>{area.label}</option>
{/each}
</select>
{#if $formErrors.area_of_villa}
<p class="text-red-500 text-xs">
{$formErrors.area_of_villa}
</p>
{/if}
</div>
{:else if col.key === "issue_type"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Issue Type</label
>
<select
name="issue_type"
class="w-full border px-3 py-2 rounded {errorClass(
'issue_type',
)}"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="" disabled selected
>Select Issue Type</option
>
{#each issueTypes as type}
<option value={type.value}>{type.label}</option>
{/each}
</select>
{#if $formErrors.issue_type}
<p class="text-red-500 text-xs">
{$formErrors.issue_type}
</p>
{/if}
</div>
{:else if col.key === "priority"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Priority</label
>
<select
name="priority"
class="w-full border px-3 py-2 rounded {errorClass(
'priority',
)}"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="" disabled selected
>Select Priority</option
>
{#each priority as p}
<option value={p.value}>{p.label}</option>
{/each}
</select>
{#if $formErrors.priority}
<p class="text-red-500 text-xs">
{$formErrors.priority}
</p>
{/if}
</div>
{:else if col.key === "villa_name"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Villa Name</label
>
<select
name="villa_name"
class="w-full border px-3 py-2 rounded {errorClass(
'villa_name',
)}"
bind:value={newIssue[col.key as keyof Issue]}
>
<option value="" disabled selected
>Select Villa</option
>
{#each dataVilla as villa}
<option value={villa.id}>{villa.name}</option>
{/each}
</select>
{#if $formErrors.villa_name}
<p class="text-red-500 text-xs">
{$formErrors.villa_name}
</p>
{/if}
</div>
{:else if col.key === "description_of_the_issue"}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>Description of The Issue</label
>
<textarea
name="description_of_the_issue"
class="w-full border px-3 py-2 rounded {errorClass(
'description_of_the_issue',
)}"
bind:value={newIssue[col.key as keyof Issue]}
placeholder={col.title}
rows="4"
></textarea>
{#if $formErrors.description_of_the_issue}
<p class="text-red-500 text-xs">
{$formErrors.description_of_the_issue}
</p>
{/if}
</div>
{:else}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>{col.title}</label
>
<input
name={col.key}
class="w-full border px-3 py-2 rounded"
type="text"
bind:value={newIssue[col.key as keyof Issue]}
placeholder={col.title}
/>
</div>
{/if}
{/each}
<div class="flex justify-end gap-2 mt-4">
<button
class="px-4 py-2 text-sm rounded bg-gray-200 hover:bg-gray-300"
on:click={() => (showModal = false)}
>
Cancel
</button>
<button
class="px-4 py-2 text-sm rounded bg-blue-600 text-white hover:bg-blue-700"
type="submit"
>
Save
</button>
</div>
</form>
</div>
{/if}