Compare commits

...

2 Commits

Author SHA1 Message Date
dhilanradya
ffe0039391 Merge branch 'production' of https://git.abbauf.com/Magang-2025/Kasir into production 2025-08-28 13:10:03 +07:00
dhilanradya
311605bd5f [update] produk + produkcard 2025-08-28 13:08:33 +07:00
2 changed files with 151 additions and 127 deletions

View File

@ -1,65 +1,31 @@
<template> <template>
<div> <div
<!-- Card Produk --> class="relative border border-C rounded-md aspect-square flex items-center justify-center hover:shadow-md transition cursor-pointer overflow-hidden"
@click="$emit('click', product.id)"
>
<!-- Foto Produk -->
<img
v-if="product.foto && product.foto.length > 0"
:src="product.foto[0].url"
:alt="product.nama"
class="w-full h-full object-cover"
/>
<span v-else class="text-gray-400 text-sm">[tidak ada foto]</span>
<!-- Nama Produk di bawah -->
<div <div
class="border border-C rounded-md aspect-square flex items-center justify-center hover:shadow-md transition cursor-pointer" class="absolute bottom-0 w-full bg-black/60 text-white text-center text-sm py-1"
@click="showDetail = true"
> >
<span class="text-gray-700 font-medium text-center px-2"> {{ product.nama }}
{{ product.nama }}
</span>
</div>
<!-- Overlay Detail -->
<div
v-if="showDetail"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div
class="bg-white rounded-lg shadow-lg w-[90%] max-w-md p-6 relative"
>
<!-- Tombol Close -->
<button
class="absolute top-3 right-3 text-gray-500 hover:text-gray-800"
@click="showDetail = false"
>
</button>
<!-- Judul -->
<h2 class="text-xl font-semibold text-D mb-4 text-center">
Detail Produk
</h2>
<!-- Data Produk -->
<div class="space-y-2 text-gray-700">
<p><span class="font-semibold">Nama:</span> {{ product.nama }}</p>
<p><span class="font-semibold">Kategori:</span> {{ product.kategori }}</p>
<p><span class="font-semibold">Berat:</span> {{ product.berat }} gram</p>
<p><span class="font-semibold">Kadar:</span> {{ product.kadar }}%</p>
<p><span class="font-semibold">Harga/gram:</span> Rp {{ formatHarga(product.harga_per_gram) }}</p>
<p><span class="font-semibold">Harga Jual:</span> Rp {{ formatHarga(product.harga_jual) }}</p>
<p><span class="font-semibold">Stok:</span> {{ product.items_count }} pcs</p>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; defineProps({
const props = defineProps({
product: { product: {
type: Object, type: Object,
required: true, required: true,
}, },
}); });
const showDetail = ref(false);
// Format rupiah
function formatHarga(value) {
return new Intl.NumberFormat("id-ID").format(value);
}
</script> </script>

View File

@ -4,14 +4,27 @@
<!-- Judul --> <!-- Judul -->
<p class="font-serif italic text-[25px] text-D">PRODUK</p> <p class="font-serif italic text-[25px] text-D">PRODUK</p>
<!-- Search --> <!-- Filter -->
<searchbar v-model:search="searchQuery" /> <div class="mt-3 flex flex-col md:flex-row md:items-center md:justify-between gap-3">
<!-- Dropdown Kategori -->
<select
v-model="selectedCategory"
class="border border-gray-300 rounded-md px-3 py-2 bg-B focus:outline-none focus:ring-2 focus:ring-B w-full md:w-48"
>
<option value="semua">Semua</option>
<option value="cincin">Cincin</option>
<option value="gelang">Gelang</option>
<option value="kalung">Kalung</option>
<option value="anting">Anting</option>
</select>
<!-- Search -->
<searchbar v-model:search="searchQuery" class="flex-1" />
</div>
<!-- Tombol Tambah Produk --> <!-- Tombol Tambah Produk -->
<div class="mt-3 flex justify-end"> <div class="mt-3 flex justify-end">
<button <button class="bg-C text-[#0a1a3c] px-4 py-2 rounded-md shadow hover:bg-C transition">
class="bg-B text-[#0a1a3c] px-4 py-2 rounded-md shadow hover:bg-C transition"
>
Tambah Produk Tambah Produk
</button> </button>
</div> </div>
@ -22,78 +35,90 @@
v-for="item in filteredProducts" v-for="item in filteredProducts"
:key="item.id" :key="item.id"
:product="item" :product="item"
@showDetail="openOverlay" @click="openOverlay(item.id)"
/> />
</div> </div>
</div> </div>
<!-- Overlay Detail Produk --> <!-- Overlay Detail Produk -->
<div <!-- Overlay Detail Produk -->
v-if="showOverlay" <div
class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50" v-if="showOverlay"
@click.self="closeOverlay" class="fixed inset-0 bg-black/30 flex justify-center items-center z-50"
> @click.self="closeOverlay"
<div >
class="bg-white rounded-lg shadow-lg p-6 w-[400px] border-2 border-[#e6d3b3] relative" <div
> class="bg-white rounded-lg shadow-lg p-6 w-[400px] border-2 border-[#e6d3b3] relative flex flex-col items-center"
<!-- Tombol Close --> @mouseleave="closeOverlay"
<button >
@click="closeOverlay" <!-- Foto Produk dengan Slider -->
class="absolute top-2 right-2 text-gray-500 hover:text-black" <div class="relative w-60 h-60 border border-[#e6d3b3] flex items-center justify-center mb-4 overflow-hidden rounded">
> <img
v-if="detail.foto && detail.foto.length > 0"
</button> :src="detail.foto[currentFotoIndex].url"
:alt="detail.nama"
class="w-full h-full object-contain"
/>
<span v-else class="text-gray-400 text-sm">[gambar]</span>
<!-- Foto Produk --> <!-- Stok (pcs) pojok kiri atas -->
<div class="border border-[#e6d3b3] p-2 mb-4 flex justify-center"> <div class="absolute top-1 left-1 bg-black/60 text-white text-xs px-2 py-1 rounded">
<img {{ detail.item_count }} pcs
v-if="detail.gambar"
:src="`http://127.0.0.1:8000/storage/${detail.gambar}`"
:alt="detail.nama"
class="w-40 h-40 object-contain"
/>
<span v-else class="text-gray-400 text-sm">[gambar]</span>
</div>
<!-- Stok -->
<p class="text-sm mb-1">{{ detail.item_count }} pcs</p>
<!-- Nama Produk -->
<h2 class="text-xl font-semibold text-center mb-3">
{{ detail.nama }}
</h2>
<!-- Detail Harga & Info -->
<div class="grid grid-cols-2 gap-2 text-sm mb-4">
<p>Harga Beli : Rp. {{ formatNumber(detail.harga_beli) }}</p>
<p class="text-right">{{ detail.kadar }} K</p>
<p>Harga Jual : Rp. {{ formatNumber(detail.harga_jual) }}</p>
<p class="text-right">{{ detail.berat }} gram</p>
<p class="col-span-2">
Harga/gram : Rp. {{ formatNumber(detail.harga_per_gram) }}
</p>
</div>
<!-- Tombol Aksi -->
<div class="flex justify-between">
<button
class="bg-yellow-400 text-black px-4 py-2 rounded font-bold"
>
Ubah
</button>
<button
class="bg-green-400 text-black px-4 py-2 rounded font-bold"
>
Tambah
</button>
<button
class="bg-red-500 text-white px-4 py-2 rounded font-bold"
>
Hapus
</button>
</div>
</div> </div>
<!-- Nama Produk di bawah -->
<div
class="absolute bottom-0 w-full bg-black/70 text-white text-center text-sm py-1"
>
{{ detail.nama }}
</div>
<!-- Tombol Prev -->
<button
v-if="detail.foto && detail.foto.length > 1"
@click.stop="prevFoto"
class="absolute left-2 bg-white/70 hover:bg-white text-black px-2 py-1 rounded"
>
</button>
<!-- Tombol Next -->
<button
v-if="detail.foto && detail.foto.length > 1"
@click.stop="nextFoto"
class="absolute right-2 bg-white/70 hover:bg-white text-black px-2 py-1 rounded"
>
</button>
</div> </div>
<!-- Detail Harga & Info -->
<div class="grid grid-cols-2 gap-2 text-sm mb-4 w-full">
<!-- harga beli dihapus -->
<p>Harga Jual : Rp. {{ formatNumber(detail.harga_jual) }}</p>
<p class="text-right">{{ detail.kadar }} K</p>
<p class="col-span-2 text-center">
Berat : {{ detail.berat }} gram
</p>
<p class="col-span-2">
Harga/gram : Rp. {{ formatNumber(detail.harga_per_gram) }}
</p>
</div>
<!-- Tombol Aksi -->
<div class="flex justify-between w-full">
<button class="bg-yellow-400 text-black px-4 py-2 rounded font-bold">
Ubah
</button>
<button class="bg-green-400 text-black px-4 py-2 rounded font-bold">
Tambah
</button>
<button class="bg-red-500 text-white px-4 py-2 rounded font-bold">
Hapus
</button>
</div>
</div>
</div>
</mainLayout> </mainLayout>
</template> </template>
@ -106,10 +131,12 @@ import searchbar from "../components/searchbar.vue";
const products = ref([]); const products = ref([]);
const searchQuery = ref(""); const searchQuery = ref("");
const selectedCategory = ref("semua");
// overlay state // overlay state
const showOverlay = ref(false); const showOverlay = ref(false);
const detail = ref({}); const detail = ref({});
const currentFotoIndex = ref(0);
// Fetch data awal // Fetch data awal
onMounted(async () => { onMounted(async () => {
@ -121,29 +148,60 @@ onMounted(async () => {
} }
}); });
// Filter // Filter gabungan (kategori + search)
const filteredProducts = computed(() => { const filteredProducts = computed(() => {
if (!searchQuery.value) return products.value; let hasil = products.value;
return products.value.filter((p) =>
p.nama.toLowerCase().includes(searchQuery.value.toLowerCase()) // filter kategori
); if (selectedCategory.value !== "semua") {
hasil = hasil.filter(
(p) => p.kategori.toLowerCase() === selectedCategory.value
);
}
// filter search
if (searchQuery.value) {
hasil = hasil.filter((p) =>
p.nama.toLowerCase().includes(searchQuery.value.toLowerCase())
);
}
return hasil;
}); });
// Fungsi buka overlay // buka overlay
async function openOverlay(id) { async function openOverlay(id) {
try { try {
const res = await axios.get(`http://127.0.0.1:8000/api/produk/${id}`); const res = await axios.get(`http://127.0.0.1:8000/api/produk/${id}`);
detail.value = res.data; detail.value = res.data;
currentFotoIndex.value = 0; // reset ke foto pertama
showOverlay.value = true; showOverlay.value = true;
} catch (error) { } catch (error) {
console.error("Gagal fetch detail produk:", error); console.error("Gagal fetch detail produk:", error);
} }
} }
// Fungsi tutup overlay // tutup overlay
function closeOverlay() { function closeOverlay() {
showOverlay.value = false; showOverlay.value = false;
detail.value = {}; detail.value = {};
currentFotoIndex.value = 0;
}
// foto navigation
function nextFoto() {
if (detail.value.foto && detail.value.foto.length > 0) {
currentFotoIndex.value =
(currentFotoIndex.value + 1) % detail.value.foto.length;
}
}
function prevFoto() {
if (detail.value.foto && detail.value.foto.length > 0) {
currentFotoIndex.value =
(currentFotoIndex.value - 1 + detail.value.foto.length) %
detail.value.foto.length;
}
} }
// Format angka // Format angka