Compare commits

..

No commits in common. "eaa331850671f7a592ced15b56b04bcf5acbeeff" and "be90b771ba5bd59ed9968b738cd645083f25323c" have entirely different histories.

9 changed files with 311 additions and 327 deletions

View File

@ -1,7 +0,0 @@
node_modules
vendor
.env
Dockerfile
docker-compose.yml
.git
.gitignore

View File

@ -1,29 +0,0 @@
# Stage 1: Build Vue
FROM node:20 as node_builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Laravel
FROM php:8.3-fpm
RUN apt-get update && apt-get install -y \
git unzip libzip-dev libpng-dev libonig-dev libxml2-dev curl \
&& docker-php-ext-install pdo_mysql zip gd mbstring exif pcntl bcmath
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Copy source code KECUALI public (biar ga ketiban build Vue)
COPY . .
# Copy hasil build Vue dari stage 1
COPY --from=node_builder /app/dist /var/www/html/public
RUN composer install --no-dev --optimize-autoloader
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
EXPOSE 9000
CMD ["php-fpm"]

View File

@ -5,7 +5,6 @@ namespace App\Http\Controllers;
use App\Models\Transaksi; use App\Models\Transaksi;
use App\Models\ItemTransaksi; use App\Models\ItemTransaksi;
use App\Models\Item; use App\Models\Item;
use App\Models\Sales;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -21,7 +20,17 @@ class TransaksiController extends Controller
} }
$transaksi = $query->get(); $transaksi = $query->get();
return response()->json($transaksi); // Ubah $mapped menjadi $transaksi jika ingin mengirim data asli // Mapping agar sesuai dengan kebutuhan frontend
$mapped = $transaksi->map(function ($trx) {
return [
'id' => $trx->id,
'tanggal' => $trx->created_at->format('d/m/Y'),
'kode' => 'TRX-' . str_pad($trx->id, 6, '0', STR_PAD_LEFT),
'pendapatan'=> $trx->total_harga,
];
});
return response()->json($mapped);
} }
@ -33,76 +42,63 @@ class TransaksiController extends Controller
} }
// Membuat transaksi baru // Membuat transaksi baru
public function store(Request $request) public function store(Request $request)
{ {
$kasir = $request->user(); // Ambil user yang login via Sanctum
if (!$kasir) { $kasir = $request->user(); // user authenticated
return response()->json(['error' => 'Unauthorized'], 401); if (!$kasir) {
} return response()->json(['error' => 'Unauthorized'], 401);
}
$request->validate([ // Validasi request (id_kasir dihapus karena otomatis dari token)
'id_sales' => 'required|exists:sales,id', $request->validate([
'nama_pembeli' => 'required|string', 'id_sales' => 'nullable|exists:sales,id',
'no_hp' => 'required|string', 'nama_sales' => 'required|string',
'alamat' => 'required|string', 'no_hp' => 'required|string',
'ongkos_bikin' => 'nullable|numeric|min:0', 'alamat' => 'required|string',
'total_harga' => 'required|numeric', 'ongkos_bikin' => 'nullable|numeric|min:0',
'items' => 'required|array', 'total_harga' => 'required|numeric',
'items.*.kode_item' => 'required|exists:items,id', 'items' => 'required|array',
'items.*.harga_deal' => 'required|numeric', 'items.*.id_item' => 'required|exists:items,id',
'items.*.harga_deal' => 'required|numeric',
]);
DB::beginTransaction();
try {
$transaksi = Transaksi::create([
'id_kasir' => $kasir->id, // ambil dari token
'id_sales' => $request->id_sales,
'nama_sales' => $request->nama_sales,
'no_hp' => $request->no_hp,
'alamat' => $request->alamat,
'ongkos_bikin' => $request->ongkos_bikin ?? 0,
'total_harga' => $request->total_harga,
]); ]);
DB::beginTransaction(); foreach ($request->items as $it) {
try { ItemTransaksi::create([
'id_transaksi' => $transaksi->id,
$sales = Sales::find($request->id_sales); 'id_item' => $it['id_item'],
'harga_deal' => $it['harga_deal'],
$transaksi = Transaksi::create([
'kode_transaksi' => 'belum pak',
'id_kasir' => $kasir->id,
'id_sales' => $request->id_sales,
'nama_sales' => $sales->nama ?? 'N/A',
'nama_pembeli' => $request->nama_pembeli,
'no_hp' => $request->no_hp,
'alamat' => $request->alamat,
'ongkos_bikin' => $request->ongkos_bikin ?? 0,
'total_harga' => $request->total_harga,
]); ]);
foreach ($request->items as $it) { Item::where('id', $it['id_item'])->update(['is_sold' => true]);
// TODO: ubah saat transaksi pake kode_item
// $item = Item::where('kode_item', $it['kode_item'])->first();
// if (!$item) {
// throw new \Exception("Item dengan kode_item {$it['kode_item']} tidak ditemukan.");
// }
$item = Item::find($it['kode_item']);
ItemTransaksi::create([
'id_transaksi' => $transaksi->id,
'id_item' => $item->id,
'harga_deal' => $it['harga_deal'],
'posisi_asal' => $item->nampan ? 'Nampan ' . $item->nampan->nama : 'Brankas',
]);
$item->update([
'is_sold' => true,
'id_nampan' => null,
]);
}
DB::commit();
return response()->json(
$transaksi->load(['itemTransaksi.item.produk.foto', 'kasir', 'sales']),
201
);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'error' => $e->getMessage(),
'trace' => $e->getTrace()
], 500);
} }
}
DB::commit();
return response()->json(
$transaksi->load(['itemTransaksi.item.produk.foto', 'kasir', 'sales']),
201
);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'error' => $e->getMessage(),
'trace' => $e->getTrace()
], 500);
}
}
// Update transaksi // Update transaksi
@ -111,12 +107,7 @@ class TransaksiController extends Controller
$transaksi = Transaksi::findOrFail($id); $transaksi = Transaksi::findOrFail($id);
$transaksi->update($request->only([ $transaksi->update($request->only([
'id_sales', 'id_sales', 'nama_sales', 'no_hp', 'alamat', 'ongkos_bikin', 'total_harga'
'nama_sales',
'no_hp',
'alamat',
'ongkos_bikin',
'total_harga'
])); ]));
return response()->json($transaksi); return response()->json($transaksi);

View File

@ -11,7 +11,7 @@ class Transaksi extends Model
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
'kode_transaksi', 'kode_transaksi', // ✅ Tambahin kolom kode transaksi
'id_kasir', 'id_kasir',
'id_sales', 'id_sales',
'nama_sales', 'nama_sales',
@ -25,21 +25,23 @@ class Transaksi extends Model
protected $hidden = ['updated_at', 'deleted_at']; protected $hidden = ['updated_at', 'deleted_at'];
// ✅ Auto-generate kode_transaksi saat create
protected static function boot() protected static function boot()
{ {
parent::boot(); parent::boot();
static::created(function ($transaksi) { // Setelah transaksi berhasil dibuat (sudah punya ID)
if (!$transaksi->kode_transaksi || $transaksi->kode_transaksi === 'belum pak') { static::created(function ($transaksi) {
$prefix = "TRS"; if (!$transaksi->kode_transaksi) {
$date = $transaksi->created_at->format('Ymd'); $prefix = "TRS";
$number = str_pad($transaksi->id, 4, '0', STR_PAD_LEFT); $date = $transaksi->created_at->format('Ymd');
$number = str_pad($transaksi->id, 4, '0', STR_PAD_LEFT);
$transaksi->kode_transaksi = $prefix . $date . $number; $transaksi->kode_transaksi = $prefix . $date . $number;
$transaksi->save(); $transaksi->save();
} }
}); });
} }
public function kasir() public function kasir()
{ {

View File

@ -1,39 +0,0 @@
services:
laravel:
build:
context: .
dockerfile: Dockerfile
container_name: laravel_app
volumes:
- .:/var/www/html
command: php artisan serve --host=0.0.0.0 --port=8000
ports:
- "8000:8000"
depends_on:
- mysql
environment:
APP_ENV: local
APP_KEY: ${APP_KEY}
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
mysql:
image: mysql:8
container_name: mysql_db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: laravel
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:

View File

@ -1,9 +1,22 @@
<template> <template>
<ConfirmDeleteModal v-if="showDeleteModal" :isOpen="showDeleteModal" title="Konfirmasi" <ConfirmDeleteModal
message="Yakin ingin menghapus item ini?" @confirm="hapusPesanan" @cancel="closeDeleteModal" /> v-if="showDeleteModal"
:isOpen="showDeleteModal"
title="Konfirmasi"
message="Yakin ingin menghapus item ini?"
@confirm="hapusPesanan"
@cancel="closeDeleteModal"
/>
<!-- ==== TAMBAHAN: Struk Overlay ==== --> <!-- ==== TAMBAHAN: Struk Overlay ==== -->
<StrukOverlay v-if="showStruk" :isOpen="showStruk" :pesanan="pesanan" :total="total" @close="closeStruk"/> <StrukOverlay
v-if="showStruk"
:isOpen="showStruk"
:pesanan="pesanan"
:total="total"
@close="closeStruk"
@confirm="simpanTransaksi"
/>
<!-- ==== END TAMBAHAN ==== --> <!-- ==== END TAMBAHAN ==== -->
<div class="p-2 sm:p-4"> <div class="p-2 sm:p-4">
@ -14,14 +27,27 @@
<!-- Input Kode Item --> <!-- Input Kode Item -->
<div> <div>
<label class="block text-sm font-medium text-D">Kode Item *</label> <label class="block text-sm font-medium text-D">Kode Item *</label>
<div class="flex flex-row justify-between mt-1 w-full rounded-md bg-A shadow-sm sm:text-sm border-B"> <div
<input type="text" v-model="kodeItem" @keyup.enter="inputItem" placeholder="Scan atau masukkan kode item" class="flex flex-row justify-between mt-1 w-full rounded-md bg-A shadow-sm sm:text-sm border-B"
class="bg-A focus:outline-none focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2 w-full rounded-l-md" /> >
<button v-if="!loadingItem" @click="inputItem" class="px-3 bg-D hover:bg-D/80 text-A rounded-r-md"> <input
type="text"
v-model="kodeItem"
@keyup.enter="inputItem"
placeholder="Scan atau masukkan kode item"
class="bg-A focus:outline-none focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2 w-full rounded-l-md"
/>
<button
v-if="!loadingItem"
@click="inputItem"
class="px-3 bg-D hover:bg-D/80 text-A rounded-r-md"
>
<i class="fas fa-arrow-right"></i> <i class="fas fa-arrow-right"></i>
</button> </button>
<div v-else class="flex items-center justify-center px-3"> <div v-else class="flex items-center justify-center px-3">
<div class="rounded-full h-5 w-5 border-b-2 border-A flex items-center justify-center"> <div
class="rounded-full h-5 w-5 border-b-2 border-A flex items-center justify-center"
>
<i class="fas fa-spinner"></i> <i class="fas fa-spinner"></i>
</div> </div>
</div> </div>
@ -31,17 +57,25 @@
<!-- Input Harga Jual --> <!-- Input Harga Jual -->
<div> <div>
<label class="block text-sm font-medium text-D">Harga Jual</label> <label class="block text-sm font-medium text-D">Harga Jual</label>
<InputField v-model="hargaJual" type="number" placeholder="Masukkan Harga Jual" /> <InputField
v-model="hargaJual"
type="number"
placeholder="Masukkan Harga Jual"
/>
</div> </div>
<!-- Tombol Aksi --> <!-- Tombol Aksi -->
<div class="flex flex-col sm:flex-row justify-between gap-2"> <div class="flex flex-col sm:flex-row justify-between gap-2">
<button @click="tambahItem" <button
class="w-full sm:w-auto px-4 py-2 rounded-md bg-C text-D font-medium hover:bg-C/80 transition"> @click="tambahItem"
class="w-full sm:w-auto px-4 py-2 rounded-md bg-C text-D font-medium hover:bg-C/80 transition"
>
Tambah Item Tambah Item
</button> </button>
<button @click="konfirmasiPenjualan" <button
class="w-full sm:w-auto px-6 py-2 rounded-md bg-D text-A font-semibold hover:bg-D/80 transition"> @click="konfirmasiPenjualan"
class="w-full sm:w-auto px-6 py-2 rounded-md bg-D text-A font-semibold hover:bg-D/80 transition"
>
Lanjut Lanjut
</button> </button>
</div> </div>
@ -60,50 +94,64 @@
<!-- Error & Info --> <!-- Error & Info -->
<div class="mb-4"> <div class="mb-4">
<p v-if="error" :class="{ 'animate-shake': error }" class="text-sm text-red-600 mt-1"> <p
v-if="error"
:class="{ 'animate-shake': error }"
class="text-sm text-red-600 mt-1"
>
{{ error }} {{ error }}
</p> </p>
<p v-if="info" class="text-sm text-C mt-1">{{ info }}</p> <p v-if="info" class="text-sm text-C mt-1">{{ info }}</p>
</div> </div>
<!-- Table Responsive --> <!-- Table Responsive -->
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full border border-B rounded-lg overflow-hidden text-xs sm:text-sm"> <table
<thead class="bg-A text-D"> class="w-full border border-B rounded-lg overflow-hidden text-xs sm:text-sm"
<tr> >
<th class="border border-B p-2 w-8">No</th> <thead class="bg-A text-D">
<th class="border border-B p-2">Nama Produk</th> <tr>
<th class="border border-B p-2">Posisi</th> <th class="border border-B p-2 w-8">No</th>
<th class="border border-B p-2">Harga</th> <th class="border border-B p-2">Nama Produk</th>
<th class="border border-B p-2 w-10"></th> <th class="border border-B p-2">Posisi</th>
</tr> <th class="border border-B p-2">Harga</th>
</thead> <th class="border border-B p-2 w-10"></th>
<tbody> </tr>
<tr v-if="pesanan.length == 0" class="text-center text-D/70"> </thead>
<td colspan="5" class="h-16 border border-B text-xs sm:text-sm"> <tbody>
Belum ada item dipesan <tr v-if="pesanan.length == 0" class="text-center text-D/70">
</td> <td colspan="5" class="h-16 border border-B text-xs sm:text-sm">
</tr> Belum ada item dipesan
<tr v-else v-for="(item, index) in pesanan" :key="index" class="hover:bg-gray-50 text-center"> </td>
<td class="border border-B p-2">{{ index + 1 }}</td> </tr>
<td class="border border-B p-2 text-left truncate max-w-[120px] sm:max-w-none"> <tr
{{ item.produk.nama }} v-else
</td> v-for="(item, index) in pesanan"
<td class="border border-B p-2 truncate max-w-[80px]"> :key="index"
{{ item.posisi ? item.posisi : "Brankas" }} class="hover:bg-gray-50 text-center"
</td> >
<td class="border border-B p-2 whitespace-nowrap"> <td class="border border-B p-2">{{ index + 1 }}</td>
Rp{{ item.harga_deal.toLocaleString() }} <td class="border border-B p-2 text-left truncate max-w-[120px] sm:max-w-none">
</td> {{ item.produk.nama }}
<td class="border border-B p-2 text-center"> </td>
<button @click="openDeleteModal(index)" class="text-red-500 hover:text-red-700"> <td class="border border-B p-2 truncate max-w-[80px]">
<i class="fas fa-trash"></i> {{ item.posisi ? item.posisi : "Brankas" }}
</button> </td>
</td> <td class="border border-B p-2 whitespace-nowrap">
</tr> Rp{{ item.harga_deal.toLocaleString() }}
</tbody> </td>
</table> <td class="border border-B p-2 text-center">
</div> <button
@click="openDeleteModal(index)"
class="text-red-500 hover:text-red-700"
>
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</template> </template>
@ -114,7 +162,9 @@ import { ref, computed } from "vue";
import InputField from "./InputField.vue"; import InputField from "./InputField.vue";
import axios from "axios"; import axios from "axios";
import ConfirmDeleteModal from "./ConfirmDeleteModal.vue"; import ConfirmDeleteModal from "./ConfirmDeleteModal.vue";
// ==== TAMBAHAN: Import StrukOverlay ====
import StrukOverlay from "./StrukOverlay.vue"; import StrukOverlay from "./StrukOverlay.vue";
// ==== END TAMBAHAN ====
const kodeItem = ref(""); const kodeItem = ref("");
const info = ref(""); const info = ref("");
@ -126,86 +176,92 @@ const pesanan = ref([]);
const showDeleteModal = ref(false) const showDeleteModal = ref(false)
const deleteIndex = ref(null) const deleteIndex = ref(null)
// ==== TAMBAHAN: State untuk struk ====
const showStruk = ref(false); const showStruk = ref(false);
// ==== END TAMBAHAN ====
// ==== TAMBAHAN: Emit untuk parent component ====
const emit = defineEmits(['transaksi-saved']);
// ==== END TAMBAHAN ====
let errorTimeout = null; let errorTimeout = null;
let infoTimeout = null; let infoTimeout = null;
const inputItem = async () => { const inputItem = async () => {
if (!kodeItem.value) return; if (!kodeItem.value) return;
info.value = "";
error.value = "";
clearTimeout(infoTimeout);
clearTimeout(errorTimeout);
loadingItem.value = true;
try {
const response = await axios.get(`/api/item/${kodeItem.value}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
item.value = response.data;
hargaJual.value = item.value.produk.harga_jual;
if (item.value.is_sold) {
throw new Error("Item sudah terjual");
}
if (pesanan.value.some((p) => p.id === item.value.id)) {
throw new Error("Item sedang dipesan");
}
info.value = `Item dipilih: ${item.value.produk.nama} dari ${item.value.posisi ? item.value.posisi : "Brankas"
}`;
infoTimeout = setTimeout(() => {
info.value = "";
}, 3000);
} catch (err) {
if (err == "") {
error.value = "Error: Item tidak ditemukan";
} else {
error.value = err;
}
info.value = ""; info.value = "";
hargaJual.value = null; error.value = "";
item.value = null; clearTimeout(infoTimeout);
clearTimeout(errorTimeout);
errorTimeout = setTimeout(() => { loadingItem.value = true;
error.value = "";
}, 3000); try {
} finally { const response = await axios.get(`/api/item/${kodeItem.value}`, {
loadingItem.value = false; headers: {
} Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
item.value = response.data;
hargaJual.value = item.value.produk.harga_jual;
if (item.value.is_sold) {
throw new Error("Item sudah terjual");
}
if (pesanan.value.some((p) => p.id === item.value.id)) {
throw new Error("Item sedang dipesan");
}
info.value = `Item dipilih: ${item.value.produk.nama} dari ${
item.value.posisi ? item.value.posisi : "Brankas"
}`;
infoTimeout = setTimeout(() => {
info.value = "";
}, 3000);
} catch (err) {
if (err == "") {
error.value = "Error: Item tidak ditemukan";
} else {
error.value = err;
}
info.value = "";
hargaJual.value = null;
item.value = null;
errorTimeout = setTimeout(() => {
error.value = "";
}, 3000);
} finally {
loadingItem.value = false;
}
}; };
const tambahItem = () => { const tambahItem = () => {
if (!item.value || !hargaJual.value) { if (!item.value || !hargaJual.value) {
error.value = "Scan atau masukkan kode item untuk dijual."; error.value = "Scan atau masukkan kode item untuk dijual.";
if (kodeItem.value) { if (kodeItem.value) {
error.value = error.value =
"Masukkan harga jual, atau input dari kode item lagi."; "Masukkan harga jual, atau input dari kode item lagi.";
}
clearTimeout(errorTimeout);
errorTimeout = setTimeout(() => {
error.value = "";
}, 3000);
return;
} }
clearTimeout(errorTimeout);
errorTimeout = setTimeout(() => {
error.value = "";
}, 3000);
return;
}
// harga deal // harga deal
item.value.kode_item = kodeItem.value; item.value.harga_deal = Number(hargaJual.value);
item.value.harga_deal = Number(hargaJual.value);
pesanan.value.push(item.value); pesanan.value.push(item.value);
// Reset input fields // Reset input fields
kodeItem.value = ""; kodeItem.value = "";
hargaJual.value = null; hargaJual.value = null;
item.value = null; item.value = null;
info.value = ""; info.value = "";
clearTimeout(infoTimeout); clearTimeout(infoTimeout);
}; };
const openDeleteModal = (index) => { const openDeleteModal = (index) => {
@ -227,31 +283,71 @@ const hapusPesanan = () => {
// ==== MODIFIKASI: konfirmasiPenjualan sekarang menampilkan struk ==== // ==== MODIFIKASI: konfirmasiPenjualan sekarang menampilkan struk ====
const konfirmasiPenjualan = () => { const konfirmasiPenjualan = () => {
if (pesanan.value.length === 0) { if (pesanan.value.length === 0) {
error.value = "Belum ada item yang dipesan."; error.value = "Belum ada item yang dipesan.";
clearTimeout(errorTimeout); clearTimeout(errorTimeout);
errorTimeout = setTimeout(() => { errorTimeout = setTimeout(() => {
error.value = ""; error.value = "";
}, 3000); }, 3000);
return; return;
} }
// Tampilkan struk overlay // Tampilkan struk overlay
showStruk.value = true; showStruk.value = true;
}; };
// ==== END MODIFIKASI ==== // ==== END MODIFIKASI ====
// ==== TAMBAHAN: Fungsi untuk menutup struk ==== // ==== TAMBAHAN: Fungsi untuk menutup struk ====
const closeStruk = () => { const closeStruk = () => {
showStruk.value = false; showStruk.value = false;
};
// ==== END TAMBAHAN ====
// ==== TAMBAHAN: Fungsi untuk menyimpan transaksi ====
const simpanTransaksi = async (dataTransaksi) => {
try {
// Siapkan data untuk API
const transaksiData = {
id_kasir: localStorage.getItem('user_id'), // Asumsi user_id disimpan di localStorage
id_sales: dataTransaksi.selectedSales?.id || null,
nama_sales: dataTransaksi.namaPembeli,
no_hp: dataTransaksi.nomorTelepon,
alamat: dataTransaksi.alamat,
ongkos_bikin: dataTransaksi.ongkosBikin || 0,
total_harga: total.value + (dataTransaksi.ongkosBikin || 0),
items: pesanan.value.map(item => ({
id_item: item.id,
harga_deal: item.harga_deal
}))
};
const response = await axios.post('/api/transaksi', transaksiData, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
// Reset form setelah berhasil
pesanan.value = [];
showStruk.value = false;
// Emit ke parent untuk refresh data transaksi
emit('transaksi-saved', response.data);
alert('Transaksi berhasil disimpan!');
} catch (error) {
console.error('Error saving transaksi:', error);
alert('Error menyimpan transaksi: ' + (error.response?.data?.message || error.message));
}
}; };
// ==== END TAMBAHAN ==== // ==== END TAMBAHAN ====
const total = computed(() => { const total = computed(() => {
let sum = 0; let sum = 0;
pesanan.value.forEach((item) => { pesanan.value.forEach((item) => {
sum += item.harga_deal; sum += item.harga_deal;
}); });
return sum; return sum;
}); });
</script> </script>

View File

@ -14,7 +14,7 @@
<tr v-for="trx in props.transaksi" :key="trx.id" class="hover:bg-A"> <tr v-for="trx in props.transaksi" :key="trx.id" class="hover:bg-A">
<td class="border border-B p-2">{{ trx.tanggal }}</td> <td class="border border-B p-2">{{ trx.tanggal }}</td>
<td class="border border-B p-2">{{ trx.kode }}</td> <td class="border border-B p-2">{{ trx.kode }}</td>
<td class="border border-B p-2">Rp{{ (trx.total_harga || 0).toLocaleString() }}</td> <td class="border border-B p-2">Rp{{ (trx.pendapatan || 0).toLocaleString() }}</td>
<td class="border border-B p-2 text-center"> <td class="border border-B p-2 text-center">
<button <button
@click="$emit('detail', trx)" @click="$emit('detail', trx)"

View File

@ -260,7 +260,7 @@ const fetchSales = async () => {
}) })
salesOptions.value = response.data.map(sales => ({ salesOptions.value = response.data.map(sales => ({
value: sales.id, value: sales,
label: sales.nama label: sales.nama
})) }))
@ -298,41 +298,14 @@ const handleSimpan = () => {
} }
// Emit data ke parent // Emit data ke parent
simpanTransaksi({ emit('confirm', {
id_sales: selectedSales.value, namaPembeli: namaPembeli.value,
nama_pembeli: namaPembeli.value, nomorTelepon: nomorTelepon.value,
no_hp: nomorTelepon.value,
alamat: alamat.value, alamat: alamat.value,
ongkosBikin: ongkosBikin.value || 0, ongkosBikin: ongkosBikin.value || 0,
total_harga: grandTotal.value, selectedSales: selectedSales.value
items: props.pesanan
}) })
} }
// ==== 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: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
// Reset form setelah berhasil
props.pesanan.value = [];
props.isOpen = false;
alert('Transaksi berhasil disimpan!');
window.location.reload();
} catch (error) {
console.error('Error saving transaksi:', error);
alert('Error menyimpan transaksi: ' + (error.response?.data?.message || error.message));
}
};
// ==== END TAMBAHAN ====
// ==== END TAMBAHAN ==== // ==== END TAMBAHAN ====
// ==== TAMBAHAN: Fetch sales saat component mounted ==== // ==== TAMBAHAN: Fetch sales saat component mounted ====

View File

@ -95,9 +95,6 @@ const fetchTransaksi = async () => {
}); });
transaksi.value = res.data; transaksi.value = res.data;
console.log("Fetched transaksi:", transaksi.value);
} catch (err) { } catch (err) {
console.error("Gagal fetch transaksi:", err); console.error("Gagal fetch transaksi:", err);
} finally { } finally {