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,597 @@
<script lang="ts">
import { supabase } from "$lib/supabaseClient";
import { onMount } from "svelte";
type Vendor = {
id: string;
name: string;
contact_type: string;
vendor_status: string;
vendor_subtype: string;
address: string;
contact_comment: string;
vendor_unik: string;
created_by: string;
created_at: string;
updated_at: string;
};
let allRowsVendor: Vendor[] = [];
let allRowsContactVendor: ContactVendor[] = [];
type columns = {
key: string;
title: string;
};
let columns: columns[] = [
{ key: "name", title: "Name" },
{ key: "vendor_type", title: "Vendor Type" },
{ key: "vendor_status", title: "Vendor Status" },
{ key: "vendor_subtype", title: "Vendor Subtype" },
{ key: "address", title: "Address" },
{ key: "vendor_unik", title: "Unique Vendor ID" },
{ key: "created_by", title: "Created By" },
{ key: "created_at", title: "Created At" },
];
onMount(async () => {
const { data: vendorData, error: vendorError } = await supabase
.from("vendor")
.select("*")
.order("created_at", { ascending: false });
if (vendorError) {
console.error("Error fetching vendors:", vendorError);
} else {
allRowsVendor = vendorData as Vendor[];
}
});
let currentPage = 1;
let itemsPerPage = 10;
$: totalPages = Math.ceil(allRowsVendor.length / itemsPerPage);
$: paginatedRows = allRowsVendor.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage,
);
function nextPage() {
if (currentPage < totalPages) {
currentPage += 1;
}
}
function previousPage() {
if (currentPage > 1) {
currentPage -= 1;
}
}
function resetPagination() {
currentPage = 1;
}
let showModal = false;
let isEditing = false;
let currentEditingId: string | null = null;
let newVendor: Record<string, any> = {};
const excludedKeys = ["id", "created_by", "created_at", "updated_at"];
$: formColumns = columns.filter((col) => !excludedKeys.includes(col.key));
function openModal(vendor?: Vendor) {
showModal = true;
isEditing = !!vendor;
currentEditingId = vendor ? vendor.id : null;
newVendor = {};
for (const col of formColumns) {
newVendor[col.key] = vendor ? vendor[col.key as keyof Vendor] : "";
}
}
async function addVendor() {
const { data, error } = await supabase
.from("vendor")
.insert([newVendor])
.select();
if (error) {
console.error("Error adding vendor:", error);
} else {
allRowsVendor.push(data[0]);
resetPagination();
showModal = false;
}
}
async function updateVendor() {
const { data, error } = await supabase
.from("vendor")
.update(newVendor)
.eq("id", currentEditingId)
.select();
if (error) {
console.error("Error updating vendor:", error);
} else {
const index = allRowsVendor.findIndex(
(v) => v.id === currentEditingId,
);
if (index !== -1) {
allRowsVendor[index] = data[0];
resetPagination();
showModal = false;
}
}
}
async function deleteVendor(vendorId: string) {
const { error } = await supabase
.from("vendor")
.delete()
.eq("id", vendorId);
if (error) {
console.error("Error deleting vendor:", error);
} else {
allRowsVendor = allRowsVendor.filter((v) => v.id !== vendorId);
resetPagination();
}
}
type ContactVendor = {
id: string;
contact_name: string;
contact_type: string;
contact_status: string;
contact_position: string;
contact_email: string;
contact_phone: string;
contact_phone_mobile: string;
urutan: number;
contact_address: string;
contact_comment: string;
vendor_id: string;
created_by: string;
created_at: string;
updated_at: string;
};
let columnsContact: columns[] = [
{ key: "contact_name", title: "Contact Name" },
{ key: "contact_type", title: "Contact Type" },
{ key: "contact_status", title: "Contact Status" },
{ key: "contact_position", title: "Position" },
{ key: "contact_email", title: "Email" },
{ key: "contact_phone", title: "Phone" },
{ key: "contact_phone_mobile", title: "Mobile" },
{ key: "urutan", title: "Order" },
];
async function fetchContactVendor(vendorId: string) {
const { data: contactData, error: contactError } = await supabase
.from("contact_vendor")
.select("*")
.eq("vendor_id", vendorId)
.order("urutan", { ascending: true });
if (contactError) {
console.error("Error fetching contact vendors:", contactError);
} else {
allRowsContactVendor = contactData as ContactVendor[];
}
}
function handleVendorClick(vendorId: string) {
fetchContactVendor(vendorId);
}
let showModalContact = false;
let showModalAddEditContact = false;
let selectedVendorId: string | null = null;
let isEditingContact = false;
let newVendorContact: Record<string, any> = {};
let excludedKeysContact = [
"id",
"created_by",
"created_at",
"updated_at",
"vendor_id",
];
$: formColumnsContact = columnsContact.filter(
(col) => !excludedKeysContact.includes(col.key),
);
function openContactModal(vendorId: string) {
selectedVendorId = vendorId;
showModalContact = true;
showModalAddEditContact = true;
}
function closeContactModal() {
showModalContact = false;
showModalAddEditContact = false;
selectedVendorId = null;
}
function openModalAddContact() {
showModalAddEditContact = true;
showModalContact = false;
}
async function addContactVendor(contact: ContactVendor) {
(contact.vendor_id as string) == selectedVendorId;
const { data, error } = await supabase
.from("contact_vendor")
.insert([contact])
.select();
if (error) {
console.error("Error adding contact vendor:", error);
} else {
allRowsContactVendor.push(data[0]);
closeContactModal();
}
}
async function updateContactVendor(contact: ContactVendor) {
const { data, error } = await supabase
.from("contact_vendor")
.update(contact)
.eq("id", contact.id)
.select();
if (error) {
console.error("Error updating contact vendor:", error);
} else {
const index = allRowsContactVendor.findIndex(
(c) => c.id === contact.id,
);
if (index !== -1) {
allRowsContactVendor[index] = data[0];
closeContactModal();
}
}
}
async function deleteContactVendor(contactId: string) {
const { error } = await supabase
.from("contact_vendor")
.delete()
.eq("id", contactId);
if (error) {
console.error("Error deleting contact vendor:", error);
} else {
allRowsContactVendor = allRowsContactVendor.filter(
(c) => c.id !== contactId,
);
closeContactModal();
}
}
// Initialize the modal state
$: showModal = false;
$: showModalContact = false;
</script>
<!-- Table untuk daftar Vendor -->
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<div>
<h2 class="text-lg font-semibold text-gray-800">Vendor List</h2>
<p class="text-sm text-gray-600">Manage your vendor and contact data</p>
</div>
<button
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm"
on:click={() => {
showModal = true;
isEditing = false;
newVendor = {};
currentEditingId = null;
}}
>
Add Vendor
</button>
</div>
<!-- Vendor Table -->
<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 (col.key)}
<th
class="px-4 py-3 text-left font-semibold text-gray-700 uppercase tracking-wider whitespace-nowrap"
>{col.title}</th
>
{/each}
<th class="px-4 py-3">Contacts</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each paginatedRows as vendor}
<tr
class="hover:bg-gray-50 transition"
on:click={() => handleVendorClick(vendor.id)}
>
{#each columns as col}
<td class="px-4 py-2 text-gray-700"
>{vendor[col.key as keyof Vendor]}</td
>
{/each}
<!-- Contact Button -->
<td class="px-4 py-2">
<button
class="bg-green-600 text-white px-2 py-1 rounded text-xs hover:bg-green-700"
on:click|stopPropagation={() => {
fetchContactVendor(vendor.id);
showModalContact = true;
}}>📞 Contacts</button
>
</td>
<td class="px-4 py-2 space-x-2">
<button
class="bg-blue-600 text-white px-2 py-1 rounded text-xs hover:bg-blue-700"
on:click|stopPropagation={() => {
openModal(vendor);
}}>✏️ Edit</button
>
<button
class="bg-red-600 text-white px-2 py-1 rounded text-xs hover:bg-red-700"
on:click|stopPropagation={() =>
deleteVendor(vendor.id)}>🗑️ Delete</button
>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="flex justify-between items-center text-sm mt-2">
<div>
Showing {(currentPage - 1) * itemsPerPage + 1}{Math.min(
currentPage * itemsPerPage,
allRowsVendor.length,
)} of {allRowsVendor.length}
</div>
<div class="space-x-2">
<button
on:click={previousPage}
disabled={currentPage === 1}
class="px-3 py-1 rounded border bg-white hover:bg-gray-100 disabled:opacity-50"
>Previous</button
>
<button
on:click={nextPage}
disabled={currentPage === totalPages}
class="px-3 py-1 rounded border bg-white hover:bg-gray-100 disabled:opacity-50"
>Next</button
>
</div>
</div>
<!-- Contact Vendor Table -->
<!-- {#if allRowsContactVendor.length > 0}
<div class="mt-6">
<h3 class="text-md font-semibold mb-2">Contact Vendors</h3>
<table class="min-w-full border divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-2">Name</th>
<th class="px-3 py-2">Position</th>
<th class="px-3 py-2">Email</th>
<th class="px-3 py-2">Phone</th>
<th class="px-3 py-2">Mobile</th>
</tr>
</thead>
<tbody class="divide-y">
{#each allRowsContactVendor as contact}
<tr class="hover:bg-gray-50">
<td class="px-3 py-2">{contact.contact_name}</td>
<td class="px-3 py-2">{contact.contact_position}</td>
<td class="px-3 py-2">{contact.contact_email}</td>
<td class="px-3 py-2">{contact.contact_phone}</td>
<td class="px-3 py-2">{contact.contact_phone_mobile}</td
>
</tr>
{/each}
</tbody>
</table>
</div>
{/if} -->
<!-- Modal for Add/Edit Vendor -->
{#if showModal}
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div
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 Vendor" : "Add New Vendor"}
</h3>
{#each formColumns as col}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>{col.title}</label
>
<input
class="w-full border px-3 py-2 rounded"
type="text"
bind:value={newVendor[col.key]}
placeholder={col.title}
/>
</div>
{/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"
on:click={isEditing ? updateVendor : addVendor}
>
Save
</button>
</div>
</div>
</div>
{/if}
<!-- Modal Contact ADD and Edit -->
{#if showModalAddEditContact}
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div
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">
{isEditingContact
? "Edit Contact Vendor"
: "Add New Contact Vendor"}
</h3>
{#each formColumnsContact as col}
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700"
>{col.title}</label
>
<input
class="w-full border px-3 py-2 rounded"
type="text"
bind:value={newVendorContact[col.key]}
placeholder={col.title}
/>
</div>
{/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={() => (showModalAddEditContact = false)}
>Cancel</button
>
<button
class="px-4 py-2 text-sm rounded bg-blue-600 text-white hover:bg-blue-700"
on:click={() =>
isEditingContact
? updateContactVendor(
newVendorContact as ContactVendor,
)
: addContactVendor(
newVendorContact as ContactVendor,
)}
>
Save
</button>
</div>
</div>
</div>
{/if}
{#if showModalContact}
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div
class="bg-white p-6 rounded-2xl shadow-2xl w-[90vw] max-w-5xl max-h-[90vh] overflow-y-auto space-y-6 transition-all duration-300"
>
<!-- Header Modal -->
<div class="flex justify-between items-center border-b pb-3">
<h3 class="text-xl font-semibold text-gray-800">
📇 Contact List
</h3>
<div class="flex gap-2 items-center">
<button
class="flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded bg-blue-600 text-white hover:bg-blue-700 shadow transition"
on:click={() => openModalAddContact()}
>
Add Contact
</button>
<button
class="text-gray-500 hover:text-red-600 text-2xl leading-none transition"
on:click={() => (showModalContact = false)}
>
×
</button>
</div>
</div>
<!-- Body Modal -->
{#if allRowsContactVendor.length > 0}
<div class="overflow-x-auto">
<table
class="min-w-full text-sm text-left border rounded-md overflow-hidden"
>
<thead
class="bg-gray-100 text-gray-700 uppercase text-xs font-semibold"
>
<tr>
{#each columnsContact as col (col.key)}
<th class="px-4 py-3 whitespace-nowrap"
>{col.title}</th
>
{/each}
<th class="px-4 py-3 whitespace-nowrap"
>Actions</th
>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{#each allRowsContactVendor as contact}
<tr class="hover:bg-gray-50 transition">
{#each columnsContact as col}
<td
class="px-4 py-2 whitespace-nowrap text-gray-800"
>
{contact[
col.key as keyof ContactVendor
]}
</td>
{/each}
<td class="px-4 py-2 whitespace-nowrap">
<div class="flex gap-2">
<button
class="flex items-center gap-1 bg-blue-500 hover:bg-blue-600 text-white px-2.5 py-1 rounded text-xs transition"
on:click|stopPropagation={() => {
isEditingContact = true;
newVendorContact = {
...contact,
};
}}
>
✏️ Edit
</button>
<button
class="flex items-center gap-1 bg-red-500 hover:bg-red-600 text-white px-2.5 py-1 rounded text-xs transition"
on:click|stopPropagation={() =>
deleteContactVendor(
contact.id,
)}
>
🗑️ Delete
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{:else}
<div class="text-center space-y-2 pt-4">
<p class="text-base font-semibold text-gray-700">
No Contacts Available
</p>
<p class="text-sm text-gray-500">
There are no contacts available for this vendor.
</p>
</div>
{/if}
</div>
</div>
{/if}