From 4223cad92f02ecddad7da04559c55537a8d82333 Mon Sep 17 00:00:00 2001 From: dhilanradya Date: Fri, 12 Sep 2025 13:58:51 +0700 Subject: [PATCH 1/5] [update] ui struk --- resources/js/components/KasirForm.vue | 9 +++ resources/js/components/StrukOverlay.vue | 85 ++++++++++++++---------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/resources/js/components/KasirForm.vue b/resources/js/components/KasirForm.vue index f29cc6a..0581db2 100644 --- a/resources/js/components/KasirForm.vue +++ b/resources/js/components/KasirForm.vue @@ -194,6 +194,15 @@ const tambahItem = () => { return; } + if (pesanan.value.length >= 2) { + error.value = "Maksimal hanya bisa memesan 2 item."; + clearTimeout(errorTimeout); + errorTimeout = setTimeout(() => { + error.value = ""; + }, 3000); + return; + } + // harga deal item.value.kode_item = kodeItem.value; item.value.harga_deal = Number(hargaJual.value); diff --git a/resources/js/components/StrukOverlay.vue b/resources/js/components/StrukOverlay.vue index bfef561..8a3802c 100644 --- a/resources/js/components/StrukOverlay.vue +++ b/resources/js/components/StrukOverlay.vue @@ -77,24 +77,44 @@ Total Harga - - - - - - {{ item.produk.nama }} - - {{ item.posisi || 'Brankas' }} - {{ item.produk.berat || '-' }} - {{ item.produk.kadar || '-' }} - Rp{{ item.harga_deal.toLocaleString() }} - 1 - Rp{{ item.harga_deal.toLocaleString() }} - - + + + + + + {{ item.produk?.nama || '' }} + + + + + {{ item.posisi || '' }} + {{ item.produk?.berat || '' }} + {{ item.produk?.kadar || '' }} + + Rp{{ item.harga_deal.toLocaleString() }} + + + 1 + + + Rp{{ item.harga_deal.toLocaleString() }} + + + + + + + - +

PERHATIAN

    @@ -108,17 +128,15 @@

    Sales

    - -
    - +

    Ongkos bikin

    diluar harga jual

    @@ -130,19 +148,15 @@

    Rp

    - -

    Rp

    -

    {{ grandTotal.toLocaleString() }}

    -
    @@ -154,22 +168,15 @@
    - - - - - - + +
    - + +
    @@ -312,7 +319,7 @@ const handleSimpan = () => { // ==== TAMBAHAN: Fungsi untuk menyimpan transaksi ==== const simpanTransaksi = async (dataTransaksi) => { console.log('Data transaksi yang akan disimpan:', dataTransaksi); - + try { const response = await axios.post('/api/transaksi', dataTransaksi, { headers: { @@ -341,6 +348,14 @@ onMounted(() => { fetchSales() } }) + +const pesananMinimal = computed(() => { + const arr = [...props.pesanan] // copy data asli + while (arr.length < 2) { + arr.push({ produk: {}, harga_deal: 0, posisi: '' }) // baris kosong + } + return arr +}) // ==== END TAMBAHAN ==== From 75648b52d97cc1a8afa771cf41c6ed2b1d3b0a0b Mon Sep 17 00:00:00 2001 From: Baghaztra Date: Fri, 12 Sep 2025 14:04:08 +0700 Subject: [PATCH 2/5] [Fix] Buat dan edit produk --- .../Controllers/FotoSementaraController.php | 34 +++- app/Http/Controllers/ProdukController.php | 167 ++++++++++-------- resources/js/pages/EditProduk.vue | 118 +++++++++---- resources/js/pages/InputProduk.vue | 81 ++++----- routes/web.php | 9 +- 5 files changed, 237 insertions(+), 172 deletions(-) diff --git a/app/Http/Controllers/FotoSementaraController.php b/app/Http/Controllers/FotoSementaraController.php index 339ad6d..b04b8c3 100644 --- a/app/Http/Controllers/FotoSementaraController.php +++ b/app/Http/Controllers/FotoSementaraController.php @@ -10,8 +10,12 @@ class FotoSementaraController extends Controller { public function upload(Request $request) { + $user = $request->user(); + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + $request->validate([ - 'id_user' => 'required|exists:users,id', 'foto' => 'required|image|mimes:jpg,jpeg,png|max:2048', ]); @@ -19,15 +23,20 @@ class FotoSementaraController extends Controller $url = asset('storage/' . $path); $foto = FotoSementara::create([ - 'id_user' => $request->id_user, + 'id_user' => $user->id, 'url' => $url, ]); return response()->json($foto, 201); } - public function hapus($id) + public function hapus(Request $request, int $id) { + $user = $request->user(); + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + $foto = FotoSementara::findOrFail($id); // Extract the relative path from the URL @@ -42,18 +51,25 @@ class FotoSementaraController extends Controller return response()->json(['message' => 'Foto berhasil dihapus']); } - public function getAll($user_id) + public function getAll(Request $request) { - $data = FotoSementara::where('id_user', $user_id); - if (!$data->exists()) { - return response()->json(['message' => 'Tidak ada foto ditemukan'], 404); + $user = $request->user(); + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); } + + $data = FotoSementara::where('id_user', $user->id)->get(); return response()->json($data); } - public function reset($user_id) + public function reset(Request $request) { - FotoSementara::where('id_user', $user_id)->delete(); + $user = $request->user(); + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + FotoSementara::where('id_user', $user->id)->delete(); return response()->json(['message' => 'Foto sementara berhasil direset']); } } diff --git a/app/Http/Controllers/ProdukController.php b/app/Http/Controllers/ProdukController.php index 7100b10..681189a 100644 --- a/app/Http/Controllers/ProdukController.php +++ b/app/Http/Controllers/ProdukController.php @@ -26,63 +26,56 @@ class ProdukController extends Controller */ public function store(Request $request) { - $validated = $request->validate([ - 'nama' => 'required|string|max:100', - 'id_kategori' => 'required|exists:kategoris,id', - 'berat' => 'required|numeric', - 'kadar' => 'required|integer', - 'harga_per_gram' => 'required|numeric', - 'harga_jual' => 'required|numeric', - 'id_user' => 'nullable|exists:users,id', - ], - [ - 'nama.required' => 'Nama produk harus diisi.', - 'id_kategori' => 'Kategori tidak valid.', - 'berat.required' => 'Berat harus diisi.', - 'kadar.required' => 'Kadar harus diisi', - 'harga_per_gram.required' => 'Harga per gram harus diisi', - 'harga_jual.required' => 'Harga jual harus diisi' - ]); + $user = $request->user(); + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + $validated = $request->validate( + [ + 'nama' => 'required|string|max:100', + 'id_kategori' => 'required|exists:kategoris,id', + 'berat' => 'required|numeric', + 'kadar' => 'required|integer', + 'harga_per_gram' => 'required|numeric', + 'harga_jual' => 'required|numeric', + ], + [ + 'nama.required' => 'Nama produk harus diisi.', + 'id_kategori' => 'Kategori tidak valid.', + 'berat.required' => 'Berat harus diisi.', + 'kadar.required' => 'Kadar harus diisi.', + 'harga_per_gram.required' => 'Harga per gram harus diisi.', + 'harga_jual.required' => 'Harga jual harus diisi.' + ] + ); DB::beginTransaction(); try { - // Create produk - $produk = Produk::create([ - 'nama' => $validated['nama'], - 'id_kategori' => $validated['id_kategori'], - 'berat' => $validated['berat'], - 'kadar' => $validated['kadar'], - 'harga_per_gram' => $validated['harga_per_gram'], - 'harga_jual' => $validated['harga_jual'], - ]); + $produk = Produk::create($validated); - // Pindahkan foto sementara ke foto permanen jika ada - if (isset($validated['id_user'])) { - $fotoSementara = FotoSementara::where('id_user', $validated['id_user'])->get(); + $fotoSementara = FotoSementara::where('id_user', $user->id)->get(); - foreach ($fotoSementara as $fs) { - Foto::create([ - 'id_produk' => $produk->id, - 'url' => $fs->url - ]); + foreach ($fotoSementara as $fs) { + Foto::create([ + 'id_produk' => $produk->id, + 'url' => $fs->url + ]); - // Hapus foto sementara setelah dipindah - $fs->delete(); - } + $fs->delete(); } DB::commit(); return response()->json([ 'message' => 'Produk berhasil dibuat', - 'data' => $produk->load('foto') + 'data' => $produk->load('foto') ], 201); - } catch (\Exception $e) { DB::rollback(); return response()->json([ 'message' => 'Gagal membuat produk', - 'error' => $e->getMessage() + 'error' => $e->getMessage() ], 500); } } @@ -96,29 +89,55 @@ class ProdukController extends Controller return response()->json($produk); } + /** + * Get the specified resource to edit. + */ + public function edit(Request $request, int $id) + { + $user = $request->user(); + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + $produk = Produk::with('foto', 'kategori')->findOrFail($id); + $foto_sementara = []; + foreach ($produk->foto as $foto) { + $foto_sementara[] = FotoSementara::create([ + 'id_user' => $user->id, + 'url' => $foto->url + ]); + } + return response()->json($produk); + } + /** * Update the specified resource in storage. */ public function update(Request $request, int $id) { - $validated = $request->validate([ - 'nama' => 'required|string|max:100', - 'id_kategori' => 'required|exists:kategoris,id', - 'berat' => 'required|numeric', - 'kadar' => 'required|integer', - 'harga_per_gram' => 'required|numeric', - 'harga_jual' => 'required|numeric', - 'id_user' => 'nullable|exists:users,id', // untuk mengambil foto sementara baru - 'hapus_foto_lama' => 'nullable|boolean', // flag untuk menghapus foto lama - ], - [ - 'nama.required' => 'Nama produk harus diisi.', - 'id_kategori' => 'Kategori tidak valid.', - 'berat.required' => 'Berat harus diisi.', - 'kadar.required' => 'Kadar harus diisi', - 'harga_per_gram.required' => 'Harga per gram harus diisi', - 'harga_jual.required' => 'Harga jual harus diisi' - ]); + $user = $request->user(); + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + $validated = $request->validate( + [ + 'nama' => 'required|string|max:100', + 'id_kategori' => 'required|exists:kategoris,id', + 'berat' => 'required|numeric', + 'kadar' => 'required|integer', + 'harga_per_gram' => 'required|numeric', + 'harga_jual' => 'required|numeric', + ], + [ + 'nama.required' => 'Nama produk harus diisi.', + 'id_kategori' => 'Kategori tidak valid.', + 'berat.required' => 'Berat harus diisi.', + 'kadar.required' => 'Kadar harus diisi', + 'harga_per_gram.required' => 'Harga per gram harus diisi', + 'harga_jual.required' => 'Harga jual harus diisi', + ] + ); DB::beginTransaction(); try { @@ -134,31 +153,29 @@ class ProdukController extends Controller 'harga_jual' => $validated['harga_jual'], ]); - // Hapus foto lama jika diminta - if (isset($validated['hapus_foto_lama']) && $validated['hapus_foto_lama']) { - foreach ($produk->foto as $foto) { - // Hapus file fisik + // Hapus foto lama + foreach ($produk->foto as $foto) { + // Hapus file fisik jika memungkinkan + try { $relativePath = str_replace(asset('storage') . '/', '', $foto->url); if (Storage::disk('public')->exists($relativePath)) { Storage::disk('public')->delete($relativePath); } - $foto->delete(); + } catch (\Exception $e) { + // Maklum Pak, soalnya kadang url aja, ga ada file fisiknya #Bagas } + $foto->delete(); } - // Tambahkan foto baru dari foto sementara jika ada - if (isset($validated['id_user'])) { - $fotoSementara = FotoSementara::where('id_user', $validated['id_user'])->get(); + $fotoSementara = FotoSementara::where('id_user', $user->id)->get(); - foreach ($fotoSementara as $fs) { - Foto::create([ - 'id_produk' => $produk->id, - 'url' => $fs->url - ]); + foreach ($fotoSementara as $fs) { + Foto::create([ + 'id_produk' => $produk->id, + 'url' => $fs->url + ]); - // Hapus foto sementara setelah dipindah - $fs->delete(); - } + $fs->delete(); } DB::commit(); @@ -167,7 +184,6 @@ class ProdukController extends Controller 'message' => 'Produk berhasil diubah', 'data' => $produk->load('foto') ], 200); - } catch (\Exception $e) { DB::rollback(); return response()->json([ @@ -203,7 +219,6 @@ class ProdukController extends Controller return response()->json([ 'message' => 'Produk berhasil dihapus.' ], 200); - } catch (\Exception $e) { DB::rollback(); return response()->json([ diff --git a/resources/js/pages/EditProduk.vue b/resources/js/pages/EditProduk.vue index b96ad4d..1d8cd70 100644 --- a/resources/js/pages/EditProduk.vue +++ b/resources/js/pages/EditProduk.vue @@ -225,6 +225,7 @@ import mainLayout from "../layouts/mainLayout.vue"; import InputField from "../components/InputField.vue"; import InputSelect from "../components/InputSelect.vue"; import CreateItemModal from "../components/CreateItemModal.vue"; +import { errorMessages } from "@vue/compiler-core"; const route = useRoute(); const router = useRouter(); @@ -250,7 +251,6 @@ const fileInput = ref(null); const openItemModal = ref(false); const editedProduct = ref(null); -const userId = ref(1); // TODO: ambil dari auth const isFormValid = computed(() => { return ( @@ -281,12 +281,14 @@ const loadKategori = async () => { }; const loadProduk = async () => { - const response = await axios.get(`/api/produk/${productId}`, { + const response = await axios.get(`/api/produk/edit/${productId}`, { headers: { Authorization: `Bearer ${localStorage.getItem("token")}`, }, }); const produk = response.data; + console.log(produk); + form.value = { nama: produk.nama, id_kategori: produk.id_kategori, @@ -295,7 +297,22 @@ const loadProduk = async () => { harga_per_gram: produk.harga_per_gram, harga_jual: produk.harga_jual, }; - uploadedImages.value = produk.foto || []; +}; + +const loadFoto = async () => { + try { + const response = await axios.get(`/api/foto`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + uploadedImages.value = response.data; + console.log(uploadedImages.value); + } catch (e) { + console.error(e); + + uploadError.value = "Gagal memuat foto"; + } }; const triggerFileInput = () => { @@ -318,35 +335,65 @@ const handleDrop = (e) => { }; const uploadFiles = async (files) => { - uploadError.value = ""; - const validFiles = files.filter( - (file) => - ["image/jpeg", "image/jpg", "image/png"].includes(file.type) && - file.size <= 2 * 1024 * 1024 - ); - if (!validFiles.length) return; - uploadLoading.value = true; - try { - for (const file of validFiles) { - const formData = new FormData(); - formData.append("foto", file); - formData.append("id_user", userId.value); - const res = await axios.post("/api/foto/upload", formData, { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - "Content-Type": "multipart/form-data", - }, - }); - uploadedImages.value.push(res.data); - } - } finally { - uploadLoading.value = false; + uploadError.value = ''; + + if (uploadedImages.value.length + files.length > 6) { + uploadError.value = 'Maksimal 6 foto yang dapat diupload'; + return; + } + + const validFiles = files.filter(file => { + const isValidType = ['image/jpeg', 'image/jpg', 'image/png'].includes(file.type); + const isValidSize = file.size <= 2 * 1024 * 1024; + + if (!isValidType) { + uploadError.value = 'Format file harus JPG, JPEG, atau PNG'; + return false; } + + if (!isValidSize) { + uploadError.value = 'Ukuran file maksimal 2MB'; + return false; + } + + return true; + }); + + if (validFiles.length === 0) return; + + uploadLoading.value = true; + + try { + for (const file of validFiles) { + const formData = new FormData(); + formData.append('foto', file); + + const response = await axios.post('/api/foto', formData, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + 'Content-Type': 'multipart/form-data', + + }, + }); + + uploadedImages.value.push(response.data); + } + + if (fileInput.value) { + fileInput.value.value = ''; + } + + } catch (error) { + console.error('Upload error:', error); + uploadError.value = error.response?.data?.message || 'Gagal mengupload foto'; + } finally { + uploadLoading.value = false; + } }; const removeImage = async (id) => { try { - await axios.delete(`http://127.0.0.1:8000/api/foto/hapus/${id}`, { + await axios.delete(`/api/foto/${id}`, { headers: { Authorization: `Bearer ${localStorage.getItem("token")}`, }, @@ -361,21 +408,16 @@ const submitForm = async () => { loading.value = true; try { await axios.put( - `/api/produk/${productId}`, - { - ...form.value, - id_user: userId.value, - }, + `/api/produk/${productId}`,form.value, { headers: { Authorization: `Bearer ${localStorage.getItem("token")}`, }, } ); - alert("Produk berhasil diupdate!"); router.push("/produk"); } catch (err) { - alert("Gagal update produk!"); + errorMessages.value = err.response?.data?.message || "Gagal menyimpan produk"; console.error(err); } finally { loading.value = false; @@ -390,8 +432,10 @@ const back = () => { router.push("/produk"); }; -onMounted(() => { - loadKategori(); - loadProduk(); +onMounted(async () => { + await loadKategori(); + await loadProduk(); + loadFoto(); }); + diff --git a/resources/js/pages/InputProduk.vue b/resources/js/pages/InputProduk.vue index 0e85548..50851a5 100644 --- a/resources/js/pages/InputProduk.vue +++ b/resources/js/pages/InputProduk.vue @@ -1,11 +1,7 @@