From fc5541b8b0eddfbfb477295fe2e1ac2bbf6b6b17 Mon Sep 17 00:00:00 2001 From: adityaalfarison Date: Thu, 28 Aug 2025 10:28:33 +0700 Subject: [PATCH 1/5] update brankaslist,traylist,tray --- .env.example | 65 ----------- resources/js/components/BrankasList.vue | 4 +- resources/js/components/TrayList.vue | 70 ++++++++---- resources/js/pages/Tray.vue | 137 ++++++++++++++++++++++-- 4 files changed, 184 insertions(+), 92 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 35db1dd..0000000 --- a/.env.example +++ /dev/null @@ -1,65 +0,0 @@ -APP_NAME=Laravel -APP_ENV=local -APP_KEY= -APP_DEBUG=true -APP_URL=http://localhost - -APP_LOCALE=en -APP_FALLBACK_LOCALE=en -APP_FAKER_LOCALE=en_US - -APP_MAINTENANCE_DRIVER=file -# APP_MAINTENANCE_STORE=database - -PHP_CLI_SERVER_WORKERS=4 - -BCRYPT_ROUNDS=12 - -LOG_CHANNEL=stack -LOG_STACK=single -LOG_DEPRECATIONS_CHANNEL=null -LOG_LEVEL=debug - -DB_CONNECTION=sqlite -# DB_HOST=127.0.0.1 -# DB_PORT=3306 -# DB_DATABASE=laravel -# DB_USERNAME=root -# DB_PASSWORD= - -SESSION_DRIVER=database -SESSION_LIFETIME=120 -SESSION_ENCRYPT=false -SESSION_PATH=/ -SESSION_DOMAIN=null - -BROADCAST_CONNECTION=log -FILESYSTEM_DISK=local -QUEUE_CONNECTION=database - -CACHE_STORE=database -# CACHE_PREFIX= - -MEMCACHED_HOST=127.0.0.1 - -REDIS_CLIENT=phpredis -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 - -MAIL_MAILER=log -MAIL_SCHEME=null -MAIL_HOST=127.0.0.1 -MAIL_PORT=2525 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_FROM_ADDRESS="hello@example.com" -MAIL_FROM_NAME="${APP_NAME}" - -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -AWS_DEFAULT_REGION=us-east-1 -AWS_BUCKET= -AWS_USE_PATH_STYLE_ENDPOINT=false - -VITE_APP_NAME="${APP_NAME}" diff --git a/resources/js/components/BrankasList.vue b/resources/js/components/BrankasList.vue index dcdb2fc..0135531 100644 --- a/resources/js/components/BrankasList.vue +++ b/resources/js/components/BrankasList.vue @@ -8,8 +8,8 @@
Product Image diff --git a/resources/js/components/TrayList.vue b/resources/js/components/TrayList.vue index eea9b85..86dd17f 100644 --- a/resources/js/components/TrayList.vue +++ b/resources/js/components/TrayList.vue @@ -26,34 +26,50 @@ >
-

{{ tray.nama }}

+

{{ tray.nama }}

- - + +
-
-
+
+
+
Product Image
-

{{ item.produk.nama }}

-

{{ item.produk.id }}

+

{{ item.produk.nama }}

+

{{ item.produk.kategori }}

+

{{ item.produk.harga_jual.toLocaleString() }}

- {{ item.berat }}g + {{ item.produk.berat }}g
@@ -85,6 +101,8 @@ const props = defineProps({ }, }); +const emit = defineEmits(["edit", "delete"]) + const trays = ref([]); const loading = ref(true); const error = ref(null); @@ -92,15 +110,29 @@ const error = ref(null); // hitung total berat const totalWeight = (tray) => { if (!tray.items) return 0; - return tray.items.reduce((sum, item) => sum + (item.berat || 0), 0); + return tray.items.reduce((sum, item) => sum + (item.produk.berat || 0), 0); }; // ambil data dari backend onMounted(async () => { try { - const res = await axios.get("/api/nampan"); - trays.value = res.data; // harus array tray dengan items - console.log("Data nampan:", res.data); + const [nampanRes, itemRes] = await Promise.all([ + axios.get("/api/nampan"), + axios.get("/api/item") + ]); + + const nampans = nampanRes.data; + const items = itemRes.data; + + // mapping items ke nampan + trays.value = nampans.map(tray => { + return { + ...tray, + items: items.filter(item => item.id_nampan === tray.id) + }; + }); + + console.log("Nampan dengan items:", trays.value); } catch (err) { error.value = err.message || "Gagal mengambil data"; } finally { diff --git a/resources/js/pages/Tray.vue b/resources/js/pages/Tray.vue index fde2d7e..a867775 100644 --- a/resources/js/pages/Tray.vue +++ b/resources/js/pages/Tray.vue @@ -1,16 +1,141 @@ \ No newline at end of file +import searchbar from '../components/searchbar.vue' +import TrayList from '../components/TrayList.vue' + +const searchQuery = ref("") // buat search +const showModal = ref(false) // <-- ini penting, biar tidak undefined +const trayName = ref("") // nama nampan baru + +// buka modal +const openModal = () => { + showModal.value = true +} + +// tutup modal +const closeModal = () => { + trayName.value = "" + editingTrayId.value = null + showModal.value = false +} +// simpan nampan baru +const saveTray = async () => { + if (!trayName.value.trim()) { + alert("Nama Nampan tidak boleh kosong") + return + } + + try { + if (editingTrayId.value) { + // mode edit + await axios.put(`/api/nampan/${editingTrayId.value}`, { nama: trayName.value }) + alert("Nampan berhasil diupdate") + } else { + // mode tambah + await axios.post("/api/nampan", { nama: trayName.value }) + alert("Nampan berhasil ditambahkan") + } + closeModal() + location.reload() + } catch (error) { + console.error(error) + alert("Gagal menyimpan nampan") + } +} + + +// kosongkan semua nampan +const emptyTray = async () => { + if (!confirm("Yakin ingin memindahkan semua item ke Brankas?")) return + + try { + await axios.post("/api/brankas", { action: "move_all_from_tray" }) + alert("Semua item berhasil dipindahkan ke Brankas") + location.reload() + } catch (error) { + console.error(error) + alert("Gagal mengosongkan nampan") + } +} + +const editTray = (tray) => { + // buka modal edit, bisa pake sama seperti modal tambah + trayName.value = tray.nama + editingTrayId.value = tray.id + showModal.value = true +} + +const deleteTray = async (id) => { + if (!confirm("Yakin ingin menghapus nampan ini?")) return + try { + await axios.delete(`/api/nampan/${id}`) + alert("Nampan berhasil dihapus") + location.reload() + } catch (error) { + console.error(error) + alert("Gagal menghapus nampan") + } +} + +const editingTrayId = ref(null) + + From 71cd60981bb2e4146f003c29801dec5546f82ee4 Mon Sep 17 00:00:00 2001 From: Baghaztra Date: Thu, 28 Aug 2025 13:07:35 +0700 Subject: [PATCH 2/5] [feat] halaman create produk --- .../Controllers/FotoSementaraController.php | 9 +- app/Http/Controllers/NampanController.php | 4 +- app/Http/Controllers/TransaksiController.php | 10 +- app/Models/Nampan.php | 9 + resources/js/components/InputField.vue | 28 ++ resources/js/components/InputSelect.vue | 31 ++ resources/js/pages/InputProduk.vue | 384 ++++++++++++++++++ resources/js/router/index.js | 6 + routes/web.php | 8 +- 9 files changed, 479 insertions(+), 10 deletions(-) create mode 100644 resources/js/components/InputField.vue create mode 100644 resources/js/components/InputSelect.vue create mode 100644 resources/js/pages/InputProduk.vue diff --git a/app/Http/Controllers/FotoSementaraController.php b/app/Http/Controllers/FotoSementaraController.php index 3aa5ecb..339ad6d 100644 --- a/app/Http/Controllers/FotoSementaraController.php +++ b/app/Http/Controllers/FotoSementaraController.php @@ -11,7 +11,7 @@ class FotoSementaraController extends Controller public function upload(Request $request) { $request->validate([ - 'id_produk' => 'required|exists:produk,id', + 'id_user' => 'required|exists:users,id', 'foto' => 'required|image|mimes:jpg,jpeg,png|max:2048', ]); @@ -19,11 +19,11 @@ class FotoSementaraController extends Controller $url = asset('storage/' . $path); $foto = FotoSementara::create([ - 'id_produk' => $request->id_produk, + 'id_user' => $request->id_user, 'url' => $url, ]); - return response()->json(['message' => 'Foto berhasil disimpan'], 201); + return response()->json($foto, 201); } public function hapus($id) @@ -45,6 +45,9 @@ class FotoSementaraController extends Controller public function getAll($user_id) { $data = FotoSementara::where('id_user', $user_id); + if (!$data->exists()) { + return response()->json(['message' => 'Tidak ada foto ditemukan'], 404); + } return response()->json($data); } diff --git a/app/Http/Controllers/NampanController.php b/app/Http/Controllers/NampanController.php index 396f90a..7bf5049 100644 --- a/app/Http/Controllers/NampanController.php +++ b/app/Http/Controllers/NampanController.php @@ -13,7 +13,7 @@ class NampanController extends Controller public function index() { return response()->json( - Nampan::withCount('items')->get() + Nampan::with('items.produk.foto')->withCount('items')->get() ); } @@ -43,7 +43,7 @@ class NampanController extends Controller public function show(int $id) { return response()->json( - Nampan::with('items')->find($id) + Nampan::with('items.produk.foto')->find($id) ); } diff --git a/app/Http/Controllers/TransaksiController.php b/app/Http/Controllers/TransaksiController.php index 0eb206a..055a1bb 100644 --- a/app/Http/Controllers/TransaksiController.php +++ b/app/Http/Controllers/TransaksiController.php @@ -13,14 +13,20 @@ class TransaksiController extends Controller // List semua transaksi public function index() { - $transaksi = Transaksi::with(['kasir', 'sales', 'items.item.produk'])->get(); + $limit = request()->query('limit', null); + $query = Transaksi::with(['kasir', 'sales', 'items.item.produk'])->latest(); + if ($limit) { + $query->limit((int)$limit); + } + $transaksi = $query->get(); + $transaksi = Transaksi::with(['kasir', 'sales', 'items.item.produk'])->latest()->limit(100)->get(); return response()->json($transaksi); } // Detail transaksi by ID public function show($id) { - $transaksi = Transaksi::with(['kasir', 'sales', 'items.item.produk'])->findOrFail($id); + $transaksi = Transaksi::with(['kasir', 'sales', 'items.item.produk.foto'])->findOrFail($id); return response()->json($transaksi); } diff --git a/app/Models/Nampan.php b/app/Models/Nampan.php index 80c8c4f..5044fba 100644 --- a/app/Models/Nampan.php +++ b/app/Models/Nampan.php @@ -13,9 +13,18 @@ class Nampan extends Model protected $fillable = [ 'nama' ]; + protected $appends = ['berat_total']; + public function items() { return $this->hasMany(Item::class, 'id_nampan'); } + + public function getBeratTotalAttribute() + { + return $this->items() + ->join('produks', 'items.id_produk', '=', 'produks.id') + ->sum('produks.berat'); + } } diff --git a/resources/js/components/InputField.vue b/resources/js/components/InputField.vue new file mode 100644 index 0000000..b82db89 --- /dev/null +++ b/resources/js/components/InputField.vue @@ -0,0 +1,28 @@ + + + diff --git a/resources/js/components/InputSelect.vue b/resources/js/components/InputSelect.vue new file mode 100644 index 0000000..1a3833e --- /dev/null +++ b/resources/js/components/InputSelect.vue @@ -0,0 +1,31 @@ + + + diff --git a/resources/js/pages/InputProduk.vue b/resources/js/pages/InputProduk.vue new file mode 100644 index 0000000..8ecfacd --- /dev/null +++ b/resources/js/pages/InputProduk.vue @@ -0,0 +1,384 @@ + + + diff --git a/resources/js/router/index.js b/resources/js/router/index.js index f7e4dec..a08aa7a 100644 --- a/resources/js/router/index.js +++ b/resources/js/router/index.js @@ -3,6 +3,7 @@ import Home from '../pages/Home.vue' import Produk from '../pages/Produk.vue' import Brankas from '../pages/Brankas.vue' import Tray from '../pages/Tray.vue' +import InputProduk from '../pages/InputProduk.vue' const routes = [ @@ -16,6 +17,11 @@ const routes = [ name: 'Produk', component: Produk }, + { + path: '/produk/baru', + name: 'Produk', + component: InputProduk + }, { path: '/brankas', name: 'Brankas', diff --git a/routes/web.php b/routes/web.php index d6cbe28..fcdea34 100644 --- a/routes/web.php +++ b/routes/web.php @@ -19,10 +19,12 @@ Route::prefix('api')->group(function () { Route::apiResource('transaksi', TransaksiController::class); Route::get('brankas', [ItemController::class, 'brankasItem']); + + // Foto Sementara Route::post('foto/upload', [FotoSementaraController::class, 'upload']); - Route::delete('foto/hapus/', [FotoSementaraController::class, 'hapus']); - Route::get('foto/', [FotoSementaraController::class, 'getAll']); - Route::delete('foto/reset/', [FotoSementaraController::class, 'reset']); + Route::delete('foto/hapus/{id}', [FotoSementaraController::class, 'hapus']); + Route::get('foto/{user_id}', [FotoSementaraController::class, 'getAll']); + Route::delete('foto/reset/{user_id}', [FotoSementaraController::class, 'reset']); }); // Frontend SPA From 311605bd5fd874a4e74c6aafc6d142027ae8fbb6 Mon Sep 17 00:00:00 2001 From: dhilanradya Date: Thu, 28 Aug 2025 13:08:33 +0700 Subject: [PATCH 3/5] [update] produk + produkcard --- package-lock.json | 2 +- resources/js/components/ProductCard.vue | 68 ++------ resources/js/pages/Produk.vue | 210 +++++++++++++++--------- 3 files changed, 152 insertions(+), 128 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a4bd74..e63f396 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "tmsc-kasir-perhiasan", + "name": "Kasir", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/js/components/ProductCard.vue b/resources/js/components/ProductCard.vue index 1253d5b..650784a 100644 --- a/resources/js/components/ProductCard.vue +++ b/resources/js/components/ProductCard.vue @@ -1,65 +1,31 @@ diff --git a/resources/js/pages/Produk.vue b/resources/js/pages/Produk.vue index 8fe0318..566dce7 100644 --- a/resources/js/pages/Produk.vue +++ b/resources/js/pages/Produk.vue @@ -4,14 +4,27 @@

PRODUK

- - + +
+ + + + + +
-
@@ -22,78 +35,90 @@ v-for="item in filteredProducts" :key="item.id" :product="item" - @showDetail="openOverlay" + @click="openOverlay(item.id)" />
-
-
- - + +
+
+ +
+ + [gambar] - -
- - [gambar] -
- - -

{{ detail.item_count }} pcs

- - -

- {{ detail.nama }} -

- - -
-

Harga Beli : Rp. {{ formatNumber(detail.harga_beli) }}

-

{{ detail.kadar }} K

-

Harga Jual : Rp. {{ formatNumber(detail.harga_jual) }}

-

{{ detail.berat }} gram

-

- Harga/gram : Rp. {{ formatNumber(detail.harga_per_gram) }} -

-
- - -
- - - -
+ +
+ {{ detail.item_count }} pcs
+ + +
+ {{ detail.nama }} +
+ + + + +
+ + +
+ +

Harga Jual : Rp. {{ formatNumber(detail.harga_jual) }}

+

{{ detail.kadar }} K

+

+ Berat : {{ detail.berat }} gram +

+

+ Harga/gram : Rp. {{ formatNumber(detail.harga_per_gram) }} +

+
+ + +
+ + + +
+
+
+ @@ -106,10 +131,12 @@ import searchbar from "../components/searchbar.vue"; const products = ref([]); const searchQuery = ref(""); +const selectedCategory = ref("semua"); // overlay state const showOverlay = ref(false); const detail = ref({}); +const currentFotoIndex = ref(0); // Fetch data awal onMounted(async () => { @@ -121,29 +148,60 @@ onMounted(async () => { } }); -// Filter +// Filter gabungan (kategori + search) const filteredProducts = computed(() => { - if (!searchQuery.value) return products.value; - return products.value.filter((p) => - p.nama.toLowerCase().includes(searchQuery.value.toLowerCase()) - ); + let hasil = products.value; + + // 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) { try { const res = await axios.get(`http://127.0.0.1:8000/api/produk/${id}`); detail.value = res.data; + currentFotoIndex.value = 0; // reset ke foto pertama showOverlay.value = true; } catch (error) { console.error("Gagal fetch detail produk:", error); } } -// Fungsi tutup overlay +// tutup overlay function closeOverlay() { showOverlay.value = false; 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 From 65923ec59c40a8550a89aae3c662fcbb62fffc6f Mon Sep 17 00:00:00 2001 From: adityaalfarison Date: Thu, 28 Aug 2025 13:10:56 +0700 Subject: [PATCH 4/5] update dropdown navbar, traylist,tray --- resources/js/components/DropdownNav.vue | 48 +++++++++ resources/js/components/Header.vue | 73 +++++++++++-- resources/js/components/TrayList.vue | 5 + resources/js/pages/Tray.vue | 138 +++++++++++++++--------- 4 files changed, 205 insertions(+), 59 deletions(-) create mode 100644 resources/js/components/DropdownNav.vue diff --git a/resources/js/components/DropdownNav.vue b/resources/js/components/DropdownNav.vue new file mode 100644 index 0000000..629ff4a --- /dev/null +++ b/resources/js/components/DropdownNav.vue @@ -0,0 +1,48 @@ + + + diff --git a/resources/js/components/Header.vue b/resources/js/components/Header.vue index 0ba4563..174bc14 100644 --- a/resources/js/components/Header.vue +++ b/resources/js/components/Header.vue @@ -1,17 +1,70 @@ \ No newline at end of file +
+ diff --git a/resources/js/components/TrayList.vue b/resources/js/components/TrayList.vue index 86dd17f..85e8124 100644 --- a/resources/js/components/TrayList.vue +++ b/resources/js/components/TrayList.vue @@ -112,6 +112,11 @@ const totalWeight = (tray) => { if (!tray.items) return 0; return tray.items.reduce((sum, item) => sum + (item.produk.berat || 0), 0); }; +// const totalWeight = (tray) => { +// if (!tray.items) return 0; +// const total = tray.items.reduce((sum, item) => sum + (item.produk.berat || 0), 0); +// return total.toFixed(2); // hasil string "12.34" +// }; // ambil data dari backend onMounted(async () => { diff --git a/resources/js/pages/Tray.vue b/resources/js/pages/Tray.vue index a867775..fd7de17 100644 --- a/resources/js/pages/Tray.vue +++ b/resources/js/pages/Tray.vue @@ -1,34 +1,54 @@ + From e5f2c9920b4030a0d3860e372b750aed2f5dd16d Mon Sep 17 00:00:00 2001 From: dhilanradya Date: Thu, 28 Aug 2025 13:46:07 +0700 Subject: [PATCH 5/5] Update index.js --- resources/js/router/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/router/index.js b/resources/js/router/index.js index a08aa7a..8c4f90c 100644 --- a/resources/js/router/index.js +++ b/resources/js/router/index.js @@ -19,7 +19,7 @@ const routes = [ }, { path: '/produk/baru', - name: 'Produk', + name: 'ProdukBaru', component: InputProduk }, {