Merge branch 'production' of https://git.abbauf.com/Magang-2025/Kasir into production

This commit is contained in:
dhilanradya 2025-09-29 16:56:03 +07:00
commit 3052aacb45
15 changed files with 183 additions and 141 deletions

View File

@ -154,7 +154,7 @@ const createItem = async () => {
success.value = true; success.value = true;
createdItem.value = response.data.data; createdItem.value = response.data.data;
console.log('Item created:', createdItem); // console.log('Item created:', createdItem);
emit('itemAdded'); // 🔔 penting emit('itemAdded'); // 🔔 penting

View File

@ -303,7 +303,7 @@ const getSortIcon = (column) => {
}; };
const handleDateChange = (newDateRange) => { const handleDateChange = (newDateRange) => {
console.log('Date range changed:', newDateRange); // console.log('Date range changed:', newDateRange);
// Reset pagination when date changes // Reset pagination when date changes
pagination.value.current_page = 1; pagination.value.current_page = 1;
fetchData(1); fetchData(1);
@ -384,9 +384,9 @@ const fetchData = async (page = 1) => {
total: response.data.nampan ? response.data.nampan.length : 0, total: response.data.nampan ? response.data.nampan.length : 0,
}; };
} }
console.log('Data laporan nampan berhasil diambil:', data.value); // console.log('Data laporan nampan berhasil diambil:', data.value);
} catch (error) { } catch (error) {
console.error('Gagal mengambil data laporan nampan:', error); console.error('Gagal mengambil data laporan nampan');
data.value = null; data.value = null;
pagination.value = { pagination.value = {
current_page: 1, current_page: 1,

View File

@ -304,7 +304,7 @@ const getSortIcon = (column) => {
}; };
const handleDateChange = (newDateRange) => { const handleDateChange = (newDateRange) => {
console.log('Date range changed:', newDateRange); // console.log('Date range changed:', newDateRange);
// Reset pagination when date changes // Reset pagination when date changes
pagination.value.current_page = 1; pagination.value.current_page = 1;
fetchData(1); fetchData(1);
@ -386,7 +386,7 @@ const fetchData = async (page = 1) => {
total: response.data.produk ? response.data.produk.length : 0, total: response.data.produk ? response.data.produk.length : 0,
}; };
} }
console.log('Data laporan produk berhasil diambil:', data.value); // console.log('Data laporan produk berhasil diambil:', data.value);
} catch (error) { } catch (error) {
console.error('Gagal mengambil data laporan produk:', error); console.error('Gagal mengambil data laporan produk:', error);
data.value = null; data.value = null;

View File

@ -202,9 +202,9 @@
}; };
onMounted(() => { onMounted(() => {
console.log("Akun.id:", props.akun.id); // console.log("Akun.id:", props.akun.id);
console.log("LoggedInId:", loggedInId.value); // console.log("LoggedInId:", loggedInId.value);
console.log("isEditingSelf:", isEditingSelf.value); // console.log("isEditingSelf:", isEditingSelf.value);
}); });
</script> </script>

View File

@ -199,7 +199,7 @@ const inputItem = async () => {
// Format harga untuk tampilan // Format harga untuk tampilan
hargaJualFormatted.value = formatNumber(item.value.produk.harga_jual); hargaJualFormatted.value = formatNumber(item.value.produk.harga_jual);
console.log(item.value); // console.log(item.value);
if (item.value.is_sold) { if (item.value.is_sold) {
throw new Error("Item sudah terjual"); throw new Error("Item sudah terjual");

View File

@ -184,7 +184,7 @@ const formatTime = (dateString) => {
const lihatDetail = async (trx) => { const lihatDetail = async (trx) => {
try { try {
isDetailLoading.value = true isDetailLoading.value = true
console.log('Fetching detail untuk transaksi:', trx.kode_transaksi) // console.log('Fetching detail untuk transaksi:', trx.kode_transaksi)
const response = await axios.get(`/api/transaksi/${trx.id}`, { const response = await axios.get(`/api/transaksi/${trx.id}`, {
headers: { headers: {
@ -192,7 +192,7 @@ const lihatDetail = async (trx) => {
}, },
}) })
console.log('Response detail transaksi:', response.data) // console.log('Response detail transaksi:', response.data)
selectedTransaksi.value = response.data selectedTransaksi.value = response.data
isDetailOpen.value = true isDetailOpen.value = true

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import { inject } from "vue"; import { inject } from "vue";
import { useRoute } from "vue-router";
const { const {
logo, logo,
@ -8,6 +9,19 @@ const {
toggleDropdown, toggleDropdown,
logout logout
} = inject('navigationData'); } = inject('navigationData');
const route = useRoute();
// Function to check if a menu item or its subItems are active
const isMenuActive = (item) => {
if (item.route) {
return route.path === item.route;
}
if (item.subItems) {
return item.subItems.some(sub => route.path === sub.route);
}
return false;
};
</script> </script>
<template> <template>
@ -24,10 +38,13 @@ const {
<div class="px-8 pb-4"> <div class="px-8 pb-4">
<div class="flex justify-around items-center gap-4"> <div class="flex justify-around items-center gap-4">
<template v-for="(item, index) in items" :key="index"> <template v-for="(item, index) in items" :key="index">
<div v-if="item.subItems" class="relative flex-1"> <div v-if="item.subItems" class="relative flex-1">
<button @click="toggleDropdown(index)" <button
class="w-full text-center text-lg text-D hover:underline cursor-pointer flex items-center justify-center gap-2 transition-colors duration-200 py-2"> @click="toggleDropdown(index)"
:class="[
'w-full text-center text-lg text-D hover:underline cursor-pointer flex items-center justify-center gap-2 transition-colors duration-200 py-2',
{ 'underline underline-offset-4': isMenuActive(item) }
]">
{{ item.label }} {{ item.label }}
<svg :class="{ 'rotate-180': openDropdownIndex === index }" <svg :class="{ 'rotate-180': openDropdownIndex === index }"
class="w-4 h-4 transition-transform duration-200" fill="none" stroke="currentColor" class="w-4 h-4 transition-transform duration-200" fill="none" stroke="currentColor"
@ -41,9 +58,13 @@ const {
<ul> <ul>
<li v-for="(sub, subIndex) in item.subItems" :key="subIndex" <li v-for="(sub, subIndex) in item.subItems" :key="subIndex"
class="hover:bg-A transition-colors duration-200"> class="hover:bg-A transition-colors duration-200">
<router-link :to="sub.route" <router-link
:to="sub.route"
@click="openDropdownIndex = null" @click="openDropdownIndex = null"
class="block w-full h-full px-4 py-2 text-D"> :class="[
'block w-full h-full px-4 py-2 text-D',
{ 'underline underline-offset-4': route.path === sub.route }
]">
{{ sub.label }} {{ sub.label }}
</router-link> </router-link>
</li> </li>
@ -51,8 +72,13 @@ const {
</div> </div>
</div> </div>
<router-link v-else :to="item.route" <router-link
class="flex-1 text-center text-lg text-D hover:underline cursor-pointer transition-colors duration-200 py-2"> v-else
:to="item.route"
:class="[
'flex-1 text-center text-lg text-D hover:underline cursor-pointer transition-colors duration-200 py-2',
{ 'underline underline-offset-4': isMenuActive(item) }
]">
{{ item.label }} {{ item.label }}
</router-link> </router-link>
</template> </template>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { inject } from "vue"; import { inject } from "vue";
import { useRoute } from "vue-router";
// Mengambil data dan fungsi yang disediakan dari komponen induk
const { const {
logo, logo,
items, items,
@ -12,21 +12,33 @@ const {
closeMobileMenu, closeMobileMenu,
logout logout
} = inject('navigationData'); } = inject('navigationData');
const route = useRoute();
// Function to check if a menu item or its subItems are active
const isMenuActive = (item) => {
if (item.route) {
return route.path === item.route;
}
if (item.subItems) {
return item.subItems.some(sub => route.path === sub.route);
}
return false;
};
</script> </script>
<template> <template>
<div class="md:hidden"> <div class="md:hidden">
<div class="bg-D h-5 shadow-lg"></div> <div class="bg-D h-5 shadow-lg"></div>
<button @click="toggleMobileMenu" <button @click="toggleMobileMenu"
:class="{ 'hidden': isMobileMenuOpen, 'block': !isMobileMenuOpen }" :class="{ 'hidden': isMobileMenuOpen, 'block': !isMobileMenuOpen }"
class="fixed top-4 left-4 text-D bg-C hover:bg-B transition-colors duration-200 p-0.5 rounded-sm z-[9999]"> class="fixed top-4 left-4 text-D bg-C hover:bg-B transition-colors duration-200 p-0.5 rounded-sm z-[9999]">
<svg class="w-7 h-7" fill="none" <svg class="w-7 h-7" fill="none"
stroke="currentColor" viewBox="0 0 24 24"> stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg> </svg>
</button> </button>
<div :class="{ 'translate-x-0': isMobileMenuOpen, '-translate-x-full': !isMobileMenuOpen }" <div :class="{ 'translate-x-0': isMobileMenuOpen, '-translate-x-full': !isMobileMenuOpen }"
class="fixed inset-y-0 left-0 w-64 bg-A transform transition-transform duration-300 ease-in-out z-50 shadow-xl"> class="fixed inset-y-0 left-0 w-64 bg-A transform transition-transform duration-300 ease-in-out z-50 shadow-xl">
@ -43,7 +55,10 @@ const {
<template v-for="(item, index) in items" :key="index"> <template v-for="(item, index) in items" :key="index">
<div v-if="item.subItems" class="px-4 py-2"> <div v-if="item.subItems" class="px-4 py-2">
<button @click="toggleDropdown(index)" <button @click="toggleDropdown(index)"
class="w-full flex justify-between items-center text-left text-lg text-D hover:bg-B rounded-md px-3 py-2 transition-colors duration-200"> :class="[
'w-full flex justify-between items-center text-left text-lg text-D hover:bg-B rounded-md px-3 py-2 transition-colors duration-200',
{ 'bg-C': isMenuActive(item) }
]">
<span>{{ item.label }}</span> <span>{{ item.label }}</span>
<svg :class="{ 'rotate-180': openDropdownIndex === index }" class="w-4 h-4 transition-transform duration-200" <svg :class="{ 'rotate-180': openDropdownIndex === index }" class="w-4 h-4 transition-transform duration-200"
fill="none" stroke="currentColor" viewBox="0 0 24 24"> fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -62,16 +77,22 @@ const {
<div v-if="openDropdownIndex === index" class="mt-2 ml-4 space-y-1 overflow-hidden"> <div v-if="openDropdownIndex === index" class="mt-2 ml-4 space-y-1 overflow-hidden">
<router-link v-for="(sub, subIndex) in item.subItems" :key="subIndex" :to="sub.route" <router-link v-for="(sub, subIndex) in item.subItems" :key="subIndex" :to="sub.route"
@click="closeMobileMenu" @click="closeMobileMenu"
class="block px-3 py-2 text-D hover:bg-B rounded-md transition-colors duration-200"> :class="[
'block px-3 py-2 text-D hover:bg-B rounded-md transition-colors duration-200',
{ 'bg-C': route.path === sub.route }
]">
{{ sub.label }} {{ sub.label }}
</router-link> </router-link>
</div> </div>
</transition> </transition>
</div> </div>
<div v-else class="px-4"> <div v-else class="px-4">
<router-link :to="item.route" @click="closeMobileMenu" <router-link :to="item.route" @click="closeMobileMenu"
class="block px-3 py-2 text-lg text-D hover:bg-B rounded-md transition-colors duration-200"> :class="[
'block px-3 py-2 text-lg text-D hover:bg-B rounded-md transition-colors duration-200',
{ 'bg-C': isMenuActive(item) }
]">
{{ item.label }} {{ item.label }}
</router-link> </router-link>
</div> </div>

View File

@ -293,7 +293,7 @@ const fetchTransaksi = async (page = 1) => {
transaksi.value = response.data.data || [] transaksi.value = response.data.data || []
pagination.value = response.data.pagination || null pagination.value = response.data.pagination || null
console.log("data", transaksi.value) // console.log("data", transaksi.value)
} catch (error) { } catch (error) {
console.error('Error fetching transaksi:', error) console.error('Error fetching transaksi:', error)
transaksi.value = [] transaksi.value = []

View File

@ -355,7 +355,7 @@ const handleSimpan = () => {
} }
const simpanTransaksi = async (dataTransaksi) => { const simpanTransaksi = async (dataTransaksi) => {
console.log('Data transaksi yang akan disimpan:', dataTransaksi); // console.log('Data transaksi yang akan disimpan:', dataTransaksi);
try { try {
const response = await axios.post('/api/transaksi', dataTransaksi, { const response = await axios.post('/api/transaksi', dataTransaksi, {

View File

@ -82,68 +82,61 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Pop-up pindah item --> <!-- Pop-up pindah item -->
<div v-if="isPopupVisible" class="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50"> <div v-if="isPopupVisible" class="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<div class="bg-white rounded-xl shadow-lg max-w-sm w-full p-6 relative"> <div class="bg-white rounded-xl shadow-lg max-w-sm w-full p-6 relative">
<div class="flex justify-center mb-2"> <div class="flex justify-center mb-2">
<div class="p-2 border rounded-lg"> <div class="p-2 border rounded-lg">
<img :src="qrCodeUrl" alt="QR Code" class="size-36" /> <img :src="qrCodeUrl" alt="QR Code" class="size-36" />
</div>
</div> </div>
</div>
<div class="text-center text-D font-bold text-lg"> <div class="text-center text-D font-bold text-lg">
{{ selectedItem.kode_item }} {{ selectedItem.kode_item }}
</div> </div>
<div class="text-center text-gray-700 font-medium mb-3"> <div class="text-center text-gray-700 font-medium mb-3">
{{ selectedItem.produk.nama }} {{ selectedItem.produk.nama }}
</div>
<div class="flex justify-center mb-4">
<button @click="printQR" class="bg-D text-A px-4 py-2 rounded hover:bg-D/80 transition">
<i class="fas fa-print mr-2"></i>Cetak
</button>
</div>
<!-- Dropdown -->
<div class="mb-4">
<label for="tray-select" class="block text-sm font-medium mb-1">Nama Nampan</label>
<InputSelect v-if="isAdmin" v-model="selectedTrayId"
:options="trays.map(tray => ({ label: tray.nama, value: tray.id }))" placeholder="Pilih Nampan"
class="mt-2" />
<div class="bg-A px-3 py-2 rounded text-D font-medium" v-else>
{{trays.find(tray => tray.id === selectedTrayId)?.nama}}
</div> </div>
</div>
<div class="flex justify-end gap-2"> <div class="flex justify-center mb-4">
<button @click="printQR" class="bg-D text-A px-4 py-2 rounded hover:bg-D/80 transition">
<i class="fas fa-print mr-2"></i>Cetak
</button>
</div>
<button @click="closePopup" class="px-4 py-2 rounded bg-gray-400 hover:bg-gray-500 text-white transition"> <!-- Dropdown -->
{{ isAdmin ? 'Batal' : 'Tutup' }} <div class="mb-4">
</button> <label for="tray-select" class="block text-sm font-medium mb-1">Nama Nampan</label>
<button @click="showDeleteConfirm = true" <InputSelect v-if="isAdmin" v-model="selectedTrayId"
class="px-4 py-2 rounded bg-red-500 text-white hover:bg-red-600 transition flex items-center"> :options="trays.map(tray => ({ label: tray.nama, value: tray.id }))" placeholder="Pilih Nampan"
<i class="fas fa-trash mr-2"></i>Hapus class="mt-2" />
</button> <div class="bg-A px-3 py-2 rounded text-D font-medium" v-else>
<button v-if="isAdmin" @click="saveMove" :disabled="!selectedTrayId" class="px-4 py-2 rounded transition" {{trays.find(tray => tray.id === selectedTrayId)?.nama}}
:class="selectedTrayId ? 'bg-C hover:bg-C/80 text-D' : 'bg-gray-400 cursor-not-allowed'"> </div>
Simpan </div>
</button>
<div class="flex justify-end gap-2">
<button @click="closePopup" class="px-4 py-2 rounded bg-gray-400 hover:bg-gray-500 text-white transition">
{{ isAdmin ? 'Batal' : 'Tutup' }}
</button>
<button @click="showDeleteConfirm = true"
class="px-4 py-2 rounded bg-red-500 text-white hover:bg-red-600 transition flex items-center">
<i class="fas fa-trash mr-2"></i>Hapus
</button>
<button v-if="isAdmin" @click="saveMove" :disabled="!selectedTrayId" class="px-4 py-2 rounded transition"
:class="selectedTrayId ? 'bg-C hover:bg-C/80 text-D' : 'bg-gray-400 cursor-not-allowed'">
Simpan
</button>
</div>
</div> </div>
</div> </div>
</div>
<!-- Modal Konfirmasi Hapus --> <!-- Modal Konfirmasi Hapus -->
<ConfirmDeleteModal <ConfirmDeleteModal :isOpen="showDeleteConfirm" title="Konfirmasi Hapus Item"
:isOpen="showDeleteConfirm" message="Apakah kamu yakin ingin menghapus item ini?" confirmText="Ya, Hapus" cancelText="Batal"
title="Konfirmasi Hapus Item" @confirm="confirmDelete" @cancel="cancelDelete" />
message="Apakah kamu yakin ingin menghapus item ini?" </div>
confirmText="Ya, Hapus"
cancelText="Batal"
@confirm="confirmDelete"
@cancel="cancelDelete"
/>
</template> </template>
<script setup> <script setup>
@ -159,6 +152,7 @@ const props = defineProps({
}); });
const emit = defineEmits(["edit", "delete"]); const emit = defineEmits(["edit", "delete"]);
const trays = ref([]); const trays = ref([]);
const loading = ref(true); const loading = ref(true);
const error = ref(null); const error = ref(null);
@ -167,6 +161,7 @@ const error = ref(null);
const isPopupVisible = ref(false); const isPopupVisible = ref(false);
const selectedItem = ref(null); const selectedItem = ref(null);
const selectedTrayId = ref(""); const selectedTrayId = ref("");
const showDeleteConfirm = ref(false);
// QR Code generator // QR Code generator
const qrCodeUrl = computed(() => { const qrCodeUrl = computed(() => {
@ -235,44 +230,23 @@ const printQR = () => {
} }
}; };
const showDeleteConfirm = ref(false);
const confirmDelete = async () => { const confirmDelete = async () => {
if (!selectedItem.value) return; if (!selectedItem.value) return;
try { try {
// Panggil API hapus item
await axios.delete(`/api/item/${selectedItem.value.id}`, { await axios.delete(`/api/item/${selectedItem.value.id}`, {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
}); });
// Tampilkan alert sukses
alert.value = { success: `Item ${selectedItem.value.kode_item} berhasil dihapus.` };
// Refresh data
await refreshData(); await refreshData();
// Tutup modal & popup
showDeleteConfirm.value = false; showDeleteConfirm.value = false;
closePopup(); closePopup();
// Auto hide alert
clearTimeout(timer.value);
timer.value = setTimeout(() => { alert.value = null; }, 3000);
} catch (err) { } catch (err) {
console.error("Gagal menghapus item:", err.response?.data || err); console.error("Gagal menghapus item:", err.response?.data || err);
alert.value = { error: err.response?.data?.message || "Gagal menghapus item. Silakan coba lagi." }; error.value = err.response?.data?.message || "Gagal menghapus item. Silakan coba lagi.";
// Auto hide alert error
clearTimeout(timer.value);
timer.value = setTimeout(() => { alert.value = null; }, 5000);
} }
}; };
const cancelDelete = () => { const cancelDelete = () => {
showDeleteConfirm.value = false; showDeleteConfirm.value = false;
}; };
@ -307,6 +281,7 @@ const saveMove = async () => {
closePopup(); closePopup();
} catch (err) { } catch (err) {
console.error("Gagal memindahkan item:", err.response?.data || err); console.error("Gagal memindahkan item:", err.response?.data || err);
error.value = err.response?.data?.message || "Gagal memindahkan item. Silakan coba lagi.";
} }
}; };
@ -345,7 +320,7 @@ const emptyTrays = computed(() => {
return trays.value.filter(tray => parseFloat(totalWeight(tray)) === 0).length; return trays.value.filter(tray => parseFloat(totalWeight(tray)) === 0).length;
}); });
// --- Ambil data nampan + item --- // Ambil data nampan + item
const refreshData = async () => { const refreshData = async () => {
try { try {
const nampanRes = await axios.get("/api/nampan", { const nampanRes = await axios.get("/api/nampan", {
@ -370,4 +345,7 @@ const filteredTrays = computed(() => {
onMounted(() => { onMounted(() => {
refreshData(); refreshData();
}); });
// Expose refreshData to parent
defineExpose({ refreshData });
</script> </script>

View File

@ -287,7 +287,7 @@ const loadProduk = async () => {
}, },
}); });
const produk = response.data; const produk = response.data;
console.log(produk); // console.log(produk);
form.value = { form.value = {
nama: produk.nama, nama: produk.nama,
@ -307,7 +307,7 @@ const loadFoto = async () => {
}, },
}); });
uploadedImages.value = response.data; uploadedImages.value = response.data;
console.log(uploadedImages.value); // console.log(uploadedImages.value);
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@ -67,7 +67,7 @@ const fetchTransaksiHariIni = async (page = 1) => {
end_date: today end_date: today
}).toString(); }).toString();
console.log('Fetching transaksi hari ini:', params); // console.log('Fetching transaksi hari ini:', params);
const res = await axios.get(`/api/transaksi?${params}`, { const res = await axios.get(`/api/transaksi?${params}`, {
headers: { headers: {
@ -80,7 +80,7 @@ const fetchTransaksiHariIni = async (page = 1) => {
pagination: res.data.pagination || null pagination: res.data.pagination || null
}; };
console.log("Transaksi hari ini:", transaksi.value); // console.log("Transaksi hari ini:", transaksi.value);
} catch (err) { } catch (err) {
console.error("Gagal fetch transaksi hari ini:", err); console.error("Gagal fetch transaksi hari ini:", err);
@ -103,7 +103,7 @@ const fetchTransaksiHariIni = async (page = 1) => {
// Handle pagination // Handle pagination
const handlePageChange = (page) => { const handlePageChange = (page) => {
console.log('Page changed to:', page); // console.log('Page changed to:', page);
if (page >= 1 && page <= (transaksi.value.pagination?.last_page || 1)) { if (page >= 1 && page <= (transaksi.value.pagination?.last_page || 1)) {
fetchTransaksiHariIni(page); fetchTransaksiHariIni(page);
@ -112,7 +112,7 @@ const handlePageChange = (page) => {
// Handle transaksi baru dari KasirForm // Handle transaksi baru dari KasirForm
const handleTransaksiSaved = async (newTransaksi) => { const handleTransaksiSaved = async (newTransaksi) => {
console.log("Transaksi baru disimpan:", newTransaksi); // console.log("Transaksi baru disimpan:", newTransaksi);
// Karena ini transaksi hari ini, selalu tambahkan ke list // Karena ini transaksi hari ini, selalu tambahkan ke list
const formattedNewTransaksi = { const formattedNewTransaksi = {
@ -139,7 +139,7 @@ const handleTransaksiSaved = async (newTransaksi) => {
} }
} }
console.log("Transaksi baru ditambahkan ke list hari ini"); // console.log("Transaksi baru ditambahkan ke list hari ini");
}; };
// Auto-refresh setiap 10 detik untuk update real-time // Auto-refresh setiap 10 detik untuk update real-time

View File

@ -109,7 +109,7 @@ const fetchKategoris = async () => {
}, },
}); });
kategori.value = response.data; kategori.value = response.data;
console.log("Data kategori:", response.data); // console.log("Data kategori:", response.data);
} catch (error) { } catch (error) {
console.error("Error fetching kategori:", error); console.error("Error fetching kategori:", error);
} finally { } finally {
@ -149,7 +149,7 @@ const confirmDelete = async () => {
Authorization: `Bearer ${localStorage.getItem("token")}`, Authorization: `Bearer ${localStorage.getItem("token")}`,
}, },
}); });
console.log("Kategori berhasil dihapus"); // console.log("Kategori berhasil dihapus");
fetchKategoris(); fetchKategoris();
} catch (error) { } catch (error) {
console.error("Error deleting kategori:", error); console.error("Error deleting kategori:", error);

View File

@ -9,7 +9,8 @@
<button @click="openModal" class="px-4 py-2 sm:px-2 sm:py-1 hover:bg-B bg-C rounded-md shadow w-full"> <button @click="openModal" class="px-4 py-2 sm:px-2 sm:py-1 hover:bg-B bg-C rounded-md shadow w-full">
Tambah Nampan Tambah Nampan
</button> </button>
<button @click="promptEmptyAllTrays" class="px-4 py-2 sm:px-2 sm:py-1 bg-red-500 hover:bg-red-600 text-white rounded-md w-full"> <button @click="promptEmptyAllTrays"
class="px-4 py-2 sm:px-2 sm:py-1 bg-red-500 hover:bg-red-600 text-white rounded-md w-full">
Kosongkan Semua Nampan Kosongkan Semua Nampan
</button> </button>
</div> </div>
@ -17,21 +18,24 @@
</div> </div>
<div class="px-6" v-if="alert"> <div class="px-6" v-if="alert">
<div v-if="alert.error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert"> <div v-if="alert.error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4"
<strong class="font-bold">Error!</strong> role="alert">
<strong class="font-bold">Error! </strong>
<span class="block sm:inline">{{ alert.error }}</span> <span class="block sm:inline">{{ alert.error }}</span>
</div> </div>
<div v-if="alert.success" class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert"> <div v-if="alert.success"
<strong class="font-bold">Success!</strong> class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
<strong class="font-bold">Success! </strong>
<span class="block sm:inline">{{ alert.success }}</span> <span class="block sm:inline">{{ alert.success }}</span>
</div> </div>
</div> </div>
<TrayList :search="searchQuery" @edit="editTray" @delete="promptDeleteTray" /> <TrayList ref="trayList" :search="searchQuery" @edit="editTray" @delete="promptDeleteTray" />
<!-- Modal untuk tambah/edit nampan --> <!-- Modal untuk tambah/edit nampan -->
<div v-if="showModal" class="fixed inset-0 bg-black/75 flex justify-center items-center z-50 backdrop-blur-sm"> <div v-if="showModal" class="fixed inset-0 bg-black/75 flex justify-center items-center z-50 backdrop-blur-sm">
<div class="bg-white rounded-lg shadow-lg p-6 w-96 transform transition-all duration-300 scale-95 opacity-0 animate-fadeIn"> <div
class="bg-white rounded-lg shadow-lg p-6 w-96 transform transition-all duration-300 scale-95 opacity-0 animate-fadeIn">
<h2 class="text-lg font-semibold mb-4 text-D"> <h2 class="text-lg font-semibold mb-4 text-D">
{{ editingTrayId ? "Edit Nampan" : "Tambah Nampan" }} {{ editingTrayId ? "Edit Nampan" : "Tambah Nampan" }}
</h2> </h2>
@ -39,22 +43,16 @@
<InputField id="tray-name" v-model="trayName" type="text" placeholder="Contoh: A1" class="mb-1" /> <InputField id="tray-name" v-model="trayName" type="text" placeholder="Contoh: A1" class="mb-1" />
<p v-if="errorCreate" class="text-red-500 text-sm mb-4">{{ errorCreate }}</p> <p v-if="errorCreate" class="text-red-500 text-sm mb-4">{{ errorCreate }}</p>
<div class="flex justify-end mt-3 gap-2"> <div class="flex justify-end mt-3 gap-2">
<button @click="closeModal" class="px-4 py-2 bg-gray-400 hover:bg-gray-500 text-white rounded-md">Batal</button> <button @click="closeModal"
class="px-4 py-2 bg-gray-400 hover:bg-gray-500 text-white rounded-md">Batal</button>
<button @click="saveTray" class="px-4 py-2 bg-C hover:bg-C/80 rounded-md text-D">Simpan</button> <button @click="saveTray" class="px-4 py-2 bg-C hover:bg-C/80 rounded-md text-D">Simpan</button>
</div> </div>
</div> </div>
</div> </div>
<!-- Komponen ConfirmDeleteModal yang diperbaiki --> <!-- Komponen ConfirmDeleteModal yang diperbaiki -->
<ConfirmDeleteModal <ConfirmDeleteModal :isOpen="isConfirmModalVisible" :title="confirmModalTitle" :message="confirmModalMessage"
:isOpen="isConfirmModalVisible" :confirmText="confirmText" :cancelText="cancelText" @confirm="handleConfirmAction" @cancel="closeConfirmModal" />
:title="confirmModalTitle"
:message="confirmModalMessage"
:confirmText="confirmText"
:cancelText="cancelText"
@confirm="handleConfirmAction"
@cancel="closeConfirmModal"
/>
</mainLayout> </mainLayout>
</template> </template>
@ -75,6 +73,7 @@ const editingTrayId = ref(null);
const errorCreate = ref(""); const errorCreate = ref("");
const timer = ref(null); const timer = ref(null);
const alert = ref(null); const alert = ref(null);
const trayList = ref(null); // Add ref for TrayList
// State untuk modal konfirmasi // State untuk modal konfirmasi
const isConfirmModalVisible = ref(false); const isConfirmModalVisible = ref(false);
@ -108,8 +107,11 @@ const saveTray = async () => {
await axios.post("/api/nampan", { nama: trayName.value }, { headers }); await axios.post("/api/nampan", { nama: trayName.value }, { headers });
alert.value = { success: "Nampan berhasil ditambahkan" }; alert.value = { success: "Nampan berhasil ditambahkan" };
} }
timer.value = setTimeout(() => { alert.value = null; }, 5000);
closeModal(); closeModal();
location.reload(); if (trayList.value) {
await trayList.value.refreshData(); // Call refreshData on TrayList
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
errorCreate.value = error.response?.data?.message || "Gagal menyimpan nampan."; errorCreate.value = error.response?.data?.message || "Gagal menyimpan nampan.";
@ -153,7 +155,10 @@ const handleConfirmAction = async () => {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
}); });
alert.value = { success: "Nampan berhasil dihapus" }; alert.value = { success: "Nampan berhasil dihapus" };
location.reload(); if (trayList.value) {
await trayList.value.refreshData(); // Call refreshData on TrayList
}
timer.value = setTimeout(() => { alert.value = null; }, 5000);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
alert.value = { error: "Gagal menghapus nampan. Silakan coba lagi." }; alert.value = { error: "Gagal menghapus nampan. Silakan coba lagi." };
@ -165,7 +170,11 @@ const handleConfirmAction = async () => {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
}); });
alert.value = { success: "Semua nampan berhasil dikosongkan" }; alert.value = { success: "Semua nampan berhasil dikosongkan" };
location.reload(); if (trayList.value) {
await trayList.value.refreshData(); // Call refreshData on TrayList
}
timer.value = setTimeout(() => { alert.value = null; }, 5000);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
alert.value = { error: "Gagal mengosongkan nampan. Silakan coba lagi." }; alert.value = { error: "Gagal mengosongkan nampan. Silakan coba lagi." };
@ -187,9 +196,17 @@ const editTray = (tray) => {
<style scoped> <style scoped>
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; transform: scale(0.95); } from {
to { opacity: 1; transform: scale(1); } opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
} }
.animate-fadeIn { .animate-fadeIn {
animation: fadeIn 0.25s ease-out forwards; animation: fadeIn 0.25s ease-out forwards;
} }