[Feat] Riwayat transaksi
This commit is contained in:
parent
e0431dfeac
commit
13e998eb77
@ -6,34 +6,79 @@ 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 App\Models\Sales;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class TransaksiController extends Controller
|
class TransaksiController extends Controller
|
||||||
{
|
{
|
||||||
// List semua transaksi
|
// List semua transaksi
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$limit = request()->query('limit', null);
|
$limit = $request->query('limit', 10);
|
||||||
$query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.produk'])->latest();
|
$page = $request->query('page', 1);
|
||||||
if ($limit) {
|
$startDate = $request->query('start_date');
|
||||||
$query->limit((int)$limit);
|
$endDate = $request->query('end_date');
|
||||||
|
|
||||||
|
$query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.produk']);
|
||||||
|
|
||||||
|
// Filter berdasarkan interval tanggal
|
||||||
|
if ($startDate && $endDate) {
|
||||||
|
$query->whereBetween('created_at', [
|
||||||
|
Carbon::parse($startDate)->startOfDay(),
|
||||||
|
Carbon::parse($endDate)->endOfDay()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// Default: hanya transaksi hari ini
|
||||||
|
elseif (!$startDate && !$endDate) {
|
||||||
|
$today = Carbon::today();
|
||||||
|
$query->whereDate('created_at', $today);
|
||||||
}
|
}
|
||||||
$transaksi = $query->get();
|
|
||||||
|
|
||||||
$transaksi->each(function ($transaksi) {
|
// Order by latest
|
||||||
|
$query->latest();
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
$transaksi = $query->paginate($limit, ['*'], 'page', $page);
|
||||||
|
|
||||||
|
// Transform data
|
||||||
|
$transaksi->getCollection()->transform(function ($transaksi) {
|
||||||
$transaksi->total_items = $transaksi->itemTransaksi->count();
|
$transaksi->total_items = $transaksi->itemTransaksi->count();
|
||||||
$transaksi->tanggal = $transaksi->created_at->format('d/m/Y');
|
$transaksi->tanggal = $transaksi->created_at->format('d/m/Y H:i');
|
||||||
});
|
$transaksi->pendapatan = $transaksi->total_harga ?? 0;
|
||||||
return response()->json($transaksi);
|
|
||||||
|
|
||||||
|
return $transaksi;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'data' => $transaksi->items(),
|
||||||
|
'pagination' => [
|
||||||
|
'current_page' => $transaksi->currentPage(),
|
||||||
|
'last_page' => $transaksi->lastPage(),
|
||||||
|
'per_page' => $transaksi->perPage(),
|
||||||
|
'total' => $transaksi->total(),
|
||||||
|
'from' => $transaksi->firstItem(),
|
||||||
|
'to' => $transaksi->lastItem(),
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detail transaksi
|
||||||
// Detail transaksi by ID
|
|
||||||
public function show($id)
|
public function show($id)
|
||||||
{
|
{
|
||||||
$transaksi = Transaksi::with(['kasir', 'sales', 'itemTransaksi.produk.foto'])->findOrFail($id);
|
$transaksi = Transaksi::with([
|
||||||
|
'kasir',
|
||||||
|
'sales',
|
||||||
|
'itemTransaksi.produk',
|
||||||
|
'itemTransaksi' => function ($query) {
|
||||||
|
$query->orderBy('created_at', 'asc');
|
||||||
|
}
|
||||||
|
])->findOrFail($id);
|
||||||
|
|
||||||
|
$transaksi->total_items = $transaksi->itemTransaksi->count();
|
||||||
|
$transaksi->tanggal = $transaksi->created_at->format('d/m/Y H:i');
|
||||||
|
$transaksi->pendapatan = $transaksi->total_harga ?? 0;
|
||||||
|
|
||||||
return response()->json($transaksi);
|
return response()->json($transaksi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,51 +1,135 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-x-auto">
|
<div class="space-y-3">
|
||||||
<h3 class="text-lg font-semibold mb-4 text-gray-800">Transaksi</h3>
|
|
||||||
<table class="w-full min-w-[500px] border border-B rounded-lg text-sm">
|
|
||||||
<thead class="bg-A text-D">
|
<!-- Summary Card -->
|
||||||
<tr>
|
<div class="mt-3 bg-A border border-C rounded-lg p-3">
|
||||||
<th class="border border-B p-2 text-left">Tanggal</th>
|
<div class="flex items-center justify-between text-sm">
|
||||||
<th class="border border-B p-2 text-left">Kode Transaksi</th>
|
<span class="text-D font-medium">Transaksi Hari Ini</span>
|
||||||
<th class="border border-B p-2 text-left">Pendapatan</th>
|
<span class="text-D-700 font-semibold">
|
||||||
<th class="border border-B p-2 text-center">Detail Item</th>
|
Rp{{ totalPendapatan.toLocaleString() }}
|
||||||
</tr>
|
</span>
|
||||||
</thead>
|
</div>
|
||||||
<tbody>
|
<div class="flex items-center justify-between text-xs text-D mt-1">
|
||||||
<tr v-for="trx in props.transaksi" :key="trx.id" class="hover:bg-A">
|
<span>{{ transaksi.length }} transaksi</span>
|
||||||
<td class="border border-B p-2">{{ trx.tanggal }}</td>
|
<span>{{ totalItems }} item terjual</span>
|
||||||
<td class="border border-B p-2">{{ trx.kode_transaksi }}</td>
|
</div>
|
||||||
<td class="border border-B p-2">Rp{{ (trx.total_harga || 0).toLocaleString() }}</td>
|
</div>
|
||||||
<td class="border border-B p-2 text-center">
|
|
||||||
<button
|
<!-- Loading State -->
|
||||||
@click="lihatDetail(trx)"
|
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||||
class="px-3 py-1 rounded-md bg-D text-A hover:bg-D/80 transition">
|
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
||||||
Detail
|
<span class="ml-2 text-gray-600 text-sm">Memuat transaksi hari ini...</span>
|
||||||
</button>
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
<!-- Table -->
|
||||||
</tbody>
|
<div v-else-if="transaksi.length > 0" class="overflow-x-auto">
|
||||||
</table>
|
<table class="w-full min-w-[500px] border border-gray-200 rounded-lg text-sm">
|
||||||
|
<thead class="bg-C text-D text-center">
|
||||||
|
<tr>
|
||||||
|
<th class="border border-gray-200 p-2">Waktu</th>
|
||||||
|
<th class="border border-gray-200 p-2">Kode Transaksi</th>
|
||||||
|
<th class="border border-gray-200 p-2">Total</th>
|
||||||
|
<th class="border border-gray-200 p-2">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="trx in transaksi" :key="trx.id" class="hover:bg-gray-50 border-b border-gray-100">
|
||||||
|
<td class="border border-gray-200 p-2">
|
||||||
|
<div class="text-xs space-y-1">
|
||||||
|
<div class="font-medium text-gray-600">{{ formatTime(trx.created_at) }}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="border border-gray-200 p-2">
|
||||||
|
{{ trx.kode_transaksi }}
|
||||||
|
</td>
|
||||||
|
<td class="border border-gray-200 p-2 text-right">
|
||||||
|
<span class="text-sm">
|
||||||
|
Rp{{ (trx.pendapatan || 0).toLocaleString() }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="border border-gray-200 p-2 text-center">
|
||||||
|
<button
|
||||||
|
@click="lihatDetail(trx)"
|
||||||
|
class="px-3 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors text-xs whitespace-nowrap"
|
||||||
|
:disabled="isDetailLoading && selectedTransaksi.id === trx.id"
|
||||||
|
>
|
||||||
|
<span v-if="isDetailLoading && selectedTransaksi.id === trx.id">
|
||||||
|
<i class="fas fa-spinner fa-spin mr-1"></i>
|
||||||
|
Loading...
|
||||||
|
</span>
|
||||||
|
<span v-else>Lihat Detail</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
</div>
|
||||||
|
<div v-if="pagination" class="mt-2 p-2 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
|
<div class="flex items-center justify-between text-xs text-gray-600">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span>Menampilkan {{ pagination.from }} - {{ pagination.to }} dari {{ pagination.total }} transaksi hari ini</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
@click="$emit('page-change', pagination.current_page - 1)"
|
||||||
|
:disabled="pagination.current_page === 1"
|
||||||
|
class="px-2 py-1 text-xs border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
← Prev
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="px-2 text-gray-700">
|
||||||
|
{{ pagination.current_page }} / {{ pagination.last_page }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="$emit('page-change', pagination.current_page + 1)"
|
||||||
|
:disabled="pagination.current_page === pagination.last_page"
|
||||||
|
class="px-2 py-1 text-xs border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div v-else-if="!loading" class="text-center py-12 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
|
<div class="text-gray-500 space-y-3">
|
||||||
|
<svg class="w-16 h-16 mx-auto text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||||
|
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1" />
|
||||||
|
</svg>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-sm font-medium text-gray-900">Belum ada transaksi</p>
|
||||||
|
<p class="text-xs text-gray-500">Hari ini masih sepi...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Modal Detail Transaksi -->
|
<!-- Modal Detail Transaksi -->
|
||||||
<StrukView
|
<StrukView
|
||||||
:isOpen="isDetailOpen"
|
:is-open="isDetailOpen"
|
||||||
:transaksi="selectedTransaksi"
|
:transaksi="selectedTransaksi"
|
||||||
@close="closeDetail"
|
@close="closeDetail"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Loading Modal -->
|
<!-- Loading Detail Modal -->
|
||||||
<div v-if="isLoading"
|
<div v-if="isDetailLoading" class="fixed inset-0 bg-black/75 flex items-center justify-center z-[10000]">
|
||||||
class="fixed inset-0 bg-black/75 flex items-center justify-center z-[10000]">
|
<div class="bg-white p-6 rounded-lg flex items-center gap-3 shadow-xl max-w-sm w-full mx-4">
|
||||||
<div class="bg-white p-6 rounded-lg flex items-center gap-3">
|
|
||||||
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
||||||
<span>Memuat detail transaksi...</span>
|
<span class="text-gray-700 text-sm">Memuat detail transaksi...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import StrukView from './StrukView.vue'
|
import StrukView from './StrukView.vue'
|
||||||
|
|
||||||
@ -53,23 +137,55 @@ const props = defineProps({
|
|||||||
transaksi: {
|
transaksi: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['detail'])
|
const emit = defineEmits(['page-change'])
|
||||||
|
|
||||||
// State untuk modal detail
|
// Modal state
|
||||||
const isDetailOpen = ref(false)
|
const isDetailOpen = ref(false)
|
||||||
const selectedTransaksi = ref({})
|
const selectedTransaksi = ref({})
|
||||||
const isLoading = ref(false)
|
const isDetailLoading = ref(false)
|
||||||
|
|
||||||
// Fungsi untuk melihat detail transaksi
|
// Computed properties
|
||||||
|
const totalPendapatan = computed(() => {
|
||||||
|
return props.transaksi.reduce((total, trx) => total + (trx.pendapatan || 0), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalItems = computed(() => {
|
||||||
|
return props.transaksi.reduce((total, trx) => total + (trx.total_items || 0), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Format functions
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('id-ID', {
|
||||||
|
weekday: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatTime = (dateString) => {
|
||||||
|
return new Date(dateString).toLocaleTimeString('id-ID', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lihat detail transaksi
|
||||||
const lihatDetail = async (trx) => {
|
const lihatDetail = async (trx) => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isDetailLoading.value = true
|
||||||
console.log('Fetching detail untuk transaksi:', trx)
|
console.log('Fetching detail untuk transaksi:', trx.kode_transaksi)
|
||||||
|
|
||||||
// Fetch detail transaksi dengan semua relasi
|
|
||||||
const response = await axios.get(`/api/transaksi/${trx.id}`, {
|
const response = await axios.get(`/api/transaksi/${trx.id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
@ -94,11 +210,11 @@ const lihatDetail = async (trx) => {
|
|||||||
|
|
||||||
alert(errorMessage)
|
alert(errorMessage)
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isDetailLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fungsi untuk menutup modal detail
|
// Tutup modal detail
|
||||||
const closeDetail = () => {
|
const closeDetail = () => {
|
||||||
isDetailOpen.value = false
|
isDetailOpen.value = false
|
||||||
selectedTransaksi.value = {}
|
selectedTransaksi.value = {}
|
||||||
|
|||||||
449
resources/js/components/RiwayatTransaksi.vue
Normal file
449
resources/js/components/RiwayatTransaksi.vue
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
<template>
|
||||||
|
<div class="my-6">
|
||||||
|
<!-- Divider -->
|
||||||
|
<hr class="border-B mb-5" />
|
||||||
|
|
||||||
|
<!-- Filter Section -->
|
||||||
|
<div class="flex flex-col lg:flex-row my-3 gap-3 lg:gap-5">
|
||||||
|
<!-- Date Range Filter -->
|
||||||
|
<div class="w-full lg:w-1/3">
|
||||||
|
<DatePicker
|
||||||
|
v-model="dateRange"
|
||||||
|
label="Filter Tanggal"
|
||||||
|
placeholder="Pilih rentang tanggal"
|
||||||
|
:max-days="31"
|
||||||
|
@change="handleDateChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Section - Improved Responsiveness -->
|
||||||
|
<div class="flex flex-col sm:flex-row w-full gap-2 lg:gap-3">
|
||||||
|
<!-- Search Input Container -->
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<InputField
|
||||||
|
placeholder="Cari kode transaksi..."
|
||||||
|
v-model="searchQuery"
|
||||||
|
class="w-full"
|
||||||
|
@input="handleSearch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reset Button -->
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<button
|
||||||
|
@click="handleResetFilter"
|
||||||
|
class="w-full sm:w-auto px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors whitespace-nowrap text-sm font-medium"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table Section -->
|
||||||
|
<div class="mt-6 overflow-x-auto">
|
||||||
|
<div class="bg-white rounded-md border border-C overflow-hidden">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-C text-D">
|
||||||
|
<th class="border-x border-C px-3 py-3 text-left">
|
||||||
|
<button @click="handleSort('created_at')"
|
||||||
|
class="flex items-center justify-between w-full text-left hover:text-D/80 transition-colors">
|
||||||
|
<span>Tanggal & Waktu</span>
|
||||||
|
<i :class="getSortIcon('created_at')" class="ml-2"></i>
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
<th class="border-x border-C px-3 py-3 text-left">
|
||||||
|
<button @click="handleSort('kode_transaksi')"
|
||||||
|
class="flex items-center justify-between w-full text-left hover:text-D/80 transition-colors">
|
||||||
|
<span>Kode Transaksi</span>
|
||||||
|
<i :class="getSortIcon('kode_transaksi')" class="ml-2"></i>
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
<th class="border-x border-C px-3 py-3 text-left">
|
||||||
|
<button @click="handleSort('total_harga')"
|
||||||
|
class="flex items-center justify-between w-full hover:text-D/80 transition-colors">
|
||||||
|
<span>Nama Pembeli</span>
|
||||||
|
<i :class="getSortIcon('total_harga')" class="ml-2"></i>
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
<th class="border-x border-C px-3 py-3 text-left">
|
||||||
|
<button @click="handleSort('total_harga')"
|
||||||
|
class="flex items-center justify-center w-full hover:text-D/80 transition-colors">
|
||||||
|
<span>Total</span>
|
||||||
|
<i :class="getSortIcon('total_harga')" class="ml-2"></i>
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
<th class="border-x border-C px-3 py-3 text-center">
|
||||||
|
<span>Jml</span>
|
||||||
|
</th>
|
||||||
|
<th class="border-r border-C px-3 py-3 text-center">
|
||||||
|
<span>Aksi</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody class="divide-y divide-C/20">
|
||||||
|
<!-- Loading Row -->
|
||||||
|
<tr v-if="loading">
|
||||||
|
<td :colspan="tableColumns" class="p-8 text-center">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-D"></div>
|
||||||
|
<span class="ml-2 text-D/70">Memuat riwayat transaksi...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Empty State Row -->
|
||||||
|
<tr v-else-if="filteredTransaksi.length === 0 && !loading">
|
||||||
|
<td :colspan="tableColumns" class="p-12 text-center">
|
||||||
|
<div class="text-D/50 space-y-2">
|
||||||
|
<svg class="w-16 h-16 mx-auto text-D/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||||
|
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||||
|
</svg>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-sm font-medium">Tidak ada transaksi ditemukan.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Data Rows -->
|
||||||
|
<template v-else v-for="trx in sortedTransaksi" :key="trx.id">
|
||||||
|
<tr class="hover:bg-A/50 transition-colors">
|
||||||
|
<!-- Tanggal & Waktu -->
|
||||||
|
<td class="border-x border-C px-3 py-3">
|
||||||
|
<div class="flex flex-row text-sm gap-2">
|
||||||
|
<div class="text-D">{{ formatDate(trx.created_at) }},</div>
|
||||||
|
<div class="text-D/60"> {{ formatTime(trx.created_at) }}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Kode Transaksi -->
|
||||||
|
<td class="text-sm border-x border-C px-3 py-3">
|
||||||
|
{{ trx.kode_transaksi }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-sm border-x border-C px-3 py-3">
|
||||||
|
{{ trx.nama_pembeli }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Total -->
|
||||||
|
<td class="text-sm border-x border-C px-3 py-3 text-center">
|
||||||
|
Rp{{ (trx.total_harga || 0).toLocaleString() }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Jumlah Item -->
|
||||||
|
<td class="text-sm border-x border-C px-3 py-3 text-center">
|
||||||
|
{{ trx.total_items || 0 }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Aksi -->
|
||||||
|
<td class="border-r border-C px-3 py-3 text-center">
|
||||||
|
<button
|
||||||
|
@click="lihatDetail(trx)"
|
||||||
|
class="inline-flex items-center px-3 py-1.5 bg-C hover:bg-C/80 text-D rounded-md text-xs font-medium transition-colors"
|
||||||
|
:disabled="isDetailLoading"
|
||||||
|
>
|
||||||
|
<i v-if="isDetailLoading && selectedTransaksi.id === trx.id"
|
||||||
|
class="fas fa-spinner fa-spin mr-1"></i>
|
||||||
|
<span>Lihat Detail</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div v-if="pagination && pagination.total > 0 && pagination.last_page > 1"
|
||||||
|
class="flex items-center justify-between gap-4 mt-6 px-1">
|
||||||
|
<div class="text-sm text-D/70">
|
||||||
|
Menampilkan {{ pagination.from }} - {{ pagination.to }} dari {{ pagination.total }} transaksi
|
||||||
|
<span v-if="filteredTransaksi.length !== pagination.per_page" class="ml-2 text-blue-600">
|
||||||
|
({{ filteredTransaksi.length }} sesuai filter)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
@click="goToPage(pagination.current_page - 1)"
|
||||||
|
:disabled="pagination.current_page === 1 || loading"
|
||||||
|
class="px-3 py-2 text-sm font-medium border rounded-md bg-A border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/50 transition-colors"
|
||||||
|
>
|
||||||
|
<i class="fas fa-chevron-left mr-1"></i>
|
||||||
|
Sebelumnya
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="text-sm text-D/70 px-3">
|
||||||
|
Halaman {{ pagination.current_page }} dari {{ pagination.last_page }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="goToPage(pagination.current_page + 1)"
|
||||||
|
:disabled="pagination.current_page === pagination.last_page || loading"
|
||||||
|
class="px-3 py-2 text-sm font-medium border rounded-md bg-A border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/50 transition-colors"
|
||||||
|
>
|
||||||
|
Berikutnya
|
||||||
|
<i class="fas fa-chevron-right ml-1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Detail Transaksi -->
|
||||||
|
<StrukView
|
||||||
|
:is-open="isDetailOpen"
|
||||||
|
:transaksi="selectedTransaksi"
|
||||||
|
@close="closeDetail"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Loading Overlay for Detail -->
|
||||||
|
<div v-if="isDetailLoading" class="fixed inset-0 bg-black/60 flex items-center justify-center z-[9999] p-4">
|
||||||
|
<div class="bg-white rounded-lg p-6 flex items-center gap-3 shadow-xl max-w-md w-full">
|
||||||
|
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-D"></div>
|
||||||
|
<span class="text-D/80">Memuat detail transaksi...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import DatePicker from '@/components/DatePicker.vue'
|
||||||
|
import InputField from '@/components/InputField.vue'
|
||||||
|
import StrukView from '@/components/StrukView.vue'
|
||||||
|
|
||||||
|
// Props & Emits
|
||||||
|
const props = defineProps({
|
||||||
|
initialData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({ data: [], pagination: null })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reactive State
|
||||||
|
const transaksi = ref(props.initialData.data || [])
|
||||||
|
const pagination = ref(props.initialData.pagination || null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const isDetailLoading = ref(false)
|
||||||
|
|
||||||
|
// Filter State
|
||||||
|
const dateRange = ref({
|
||||||
|
start: new Date().toISOString().split('T')[0],
|
||||||
|
end: new Date().toISOString().split('T')[0]
|
||||||
|
})
|
||||||
|
const statusDipilih = ref('')
|
||||||
|
const pembayaranDipilih = ref('')
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
// Sort State
|
||||||
|
const sortField = ref('created_at')
|
||||||
|
const sortDirection = ref('desc')
|
||||||
|
|
||||||
|
// Modal State
|
||||||
|
const isDetailOpen = ref(false)
|
||||||
|
const selectedTransaksi = ref({})
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const filteredTransaksi = computed(() => {
|
||||||
|
let filtered = [...transaksi.value]
|
||||||
|
|
||||||
|
// Date filter
|
||||||
|
if (dateRange.value.start && dateRange.value.end) {
|
||||||
|
const startDate = new Date(dateRange.value.start)
|
||||||
|
const endDate = new Date(dateRange.value.end)
|
||||||
|
endDate.setHours(23, 59, 59, 999)
|
||||||
|
|
||||||
|
filtered = filtered.filter(trx => {
|
||||||
|
const trxDate = new Date(trx.created_at)
|
||||||
|
return trxDate >= startDate && trxDate <= endDate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status filter
|
||||||
|
if (statusDipilih.value) {
|
||||||
|
filtered = filtered.filter(trx => trx.status === statusDipilih.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payment method filter
|
||||||
|
if (pembayaranDipilih.value) {
|
||||||
|
filtered = filtered.filter(trx => trx.metode_pembayaran === pembayaranDipilih.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search filter
|
||||||
|
if (searchQuery.value) {
|
||||||
|
const query = searchQuery.value.toLowerCase()
|
||||||
|
filtered = filtered.filter(trx =>
|
||||||
|
trx.kode_transaksi.toLowerCase().includes(query) ||
|
||||||
|
(trx.nama_pelanggan && trx.nama_pelanggan.toLowerCase().includes(query))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortedTransaksi = computed(() => {
|
||||||
|
return [...filteredTransaksi.value].sort((a, b) => {
|
||||||
|
let aVal = a[sortField.value] || ''
|
||||||
|
let bVal = b[sortField.value] || ''
|
||||||
|
|
||||||
|
// Handle numeric fields
|
||||||
|
if (['total_harga', 'total_items'].includes(sortField.value)) {
|
||||||
|
aVal = parseFloat(aVal) || 0
|
||||||
|
bVal = parseFloat(bVal) || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aVal < bVal) return sortDirection.value === 'asc' ? -1 : 1
|
||||||
|
if (aVal > bVal) return sortDirection.value === 'asc' ? 1 : -1
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableColumns = computed(() => 7)
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const fetchTransaksi = async (page = 1) => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page,
|
||||||
|
limit: 10,
|
||||||
|
start_date: dateRange.value.start,
|
||||||
|
end_date: dateRange.value.end,
|
||||||
|
status: statusDipilih.value,
|
||||||
|
search: searchQuery.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await axios.get(`/api/transaksi?${params}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
transaksi.value = response.data.data || []
|
||||||
|
pagination.value = response.data.pagination || null
|
||||||
|
|
||||||
|
console.log("trans:", transaksi.value);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching transaksi:', error)
|
||||||
|
alert('Gagal memuat data transaksi: ' + (error.response?.data?.message || error.message))
|
||||||
|
transaksi.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDateChange = (newRange) => {
|
||||||
|
dateRange.value = newRange
|
||||||
|
pagination.value = null // Reset pagination
|
||||||
|
fetchTransaksi(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
// Debounce search (optional)
|
||||||
|
clearTimeout(window.searchTimeout)
|
||||||
|
window.searchTimeout = setTimeout(() => {
|
||||||
|
pagination.value = null
|
||||||
|
fetchTransaksi(1)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSort = (field) => {
|
||||||
|
if (sortField.value === field) {
|
||||||
|
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
|
||||||
|
} else {
|
||||||
|
sortField.value = field
|
||||||
|
sortDirection.value = 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTransaksi(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSortIcon = (field) => {
|
||||||
|
if (sortField.value !== field) return 'fas fa-sort text-D/40'
|
||||||
|
|
||||||
|
if (sortDirection.value === 'asc') return 'fas fa-sort-up text-D'
|
||||||
|
return 'fas fa-sort-down text-D'
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToPage = (page) => {
|
||||||
|
if (page >= 1 && page <= (pagination.value?.last_page || 1)) {
|
||||||
|
fetchTransaksi(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lihatDetail = async (trx) => {
|
||||||
|
try {
|
||||||
|
isDetailLoading.value = true
|
||||||
|
selectedTransaksi.value = trx // Show loading state first
|
||||||
|
|
||||||
|
const response = await axios.get(`/api/transaksi/${trx.id}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
selectedTransaksi.value = {
|
||||||
|
...response.data,
|
||||||
|
total_items: response.data.itemTransaksi?.length || 0
|
||||||
|
}
|
||||||
|
isDetailOpen.value = true
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching detail:', error)
|
||||||
|
alert('Gagal memuat detail transaksi: ' + (error.response?.data?.message || error.message))
|
||||||
|
} finally {
|
||||||
|
isDetailLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeDetail = () => {
|
||||||
|
isDetailOpen.value = false
|
||||||
|
selectedTransaksi.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetFilter = () => {
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
dateRange.value = { start: today, end: today }
|
||||||
|
statusDipilih.value = ''
|
||||||
|
pembayaranDipilih.value = ''
|
||||||
|
searchQuery.value = ''
|
||||||
|
fetchTransaksi(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper Functions
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatTime = (dateString) => {
|
||||||
|
return new Date(dateString).toLocaleTimeString('id-ID', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
onMounted(() => {
|
||||||
|
fetchTransaksi()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Watchers
|
||||||
|
import { watch } from 'vue'
|
||||||
|
|
||||||
|
watch([statusDipilih, pembayaranDipilih], () => {
|
||||||
|
pagination.value = null
|
||||||
|
fetchTransaksi(1)
|
||||||
|
}, { deep: true })
|
||||||
|
</script>
|
||||||
@ -7,7 +7,7 @@
|
|||||||
<!-- Left Section - Form Kasir -->
|
<!-- Left Section - Form Kasir -->
|
||||||
<div class="lg:col-span-3">
|
<div class="lg:col-span-3">
|
||||||
<div
|
<div
|
||||||
class="bg-white rounded-xl shadow-lg border border-B overflow-hidden h-auto lg:h-full"
|
class="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden h-auto lg:h-full"
|
||||||
>
|
>
|
||||||
<div class="p-2 sm:p-3 md:p-4 h-auto lg:h-full">
|
<div class="p-2 sm:p-3 md:p-4 h-auto lg:h-full">
|
||||||
<KasirForm @transaksi-saved="handleTransaksiSaved" />
|
<KasirForm @transaksi-saved="handleTransaksiSaved" />
|
||||||
@ -18,49 +18,15 @@
|
|||||||
<!-- Right Section - Transaction List -->
|
<!-- Right Section - Transaction List -->
|
||||||
<div class="lg:col-span-2">
|
<div class="lg:col-span-2">
|
||||||
<div
|
<div
|
||||||
class="bg-white rounded-xl shadow-lg border border-B overflow-hidden lg:h-fit sticky top-4 max-h-[70vh] overflow-y-auto"
|
class="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden lg:h-fit sticky top-4 max-h-[70vh] overflow-y-auto"
|
||||||
>
|
>
|
||||||
<div class="p-3 sm:p-4 md:p-6">
|
<div class="p-3 sm:p-4 md:p-6">
|
||||||
<!-- Loading -->
|
<!-- Simplified Transaction List - Hari Ini Only -->
|
||||||
<div
|
|
||||||
v-if="loading"
|
|
||||||
class="flex items-center justify-center py-8"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="animate-spin rounded-full h-8 w-8 border-b-2 border-C"
|
|
||||||
></div>
|
|
||||||
<span class="ml-3 text-D/70"
|
|
||||||
>Memuat transaksi...</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Empty -->
|
|
||||||
<div
|
|
||||||
v-else-if="!transaksi.length"
|
|
||||||
class="text-center py-8"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="w-16 h-16 mx-auto text-B mb-4"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="1"
|
|
||||||
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<p class="text-[var(--color-D)]/60 text-sm">
|
|
||||||
Belum ada transaksi
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Transaction List -->
|
|
||||||
<KasirTransaksiList
|
<KasirTransaksiList
|
||||||
v-else
|
:transaksi="transaksi.data || []"
|
||||||
:transaksi="transaksi"
|
:loading="loading"
|
||||||
|
:pagination="transaksi.pagination || null"
|
||||||
|
@page-change="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -78,36 +44,130 @@ import mainLayout from "../layouts/mainLayout.vue";
|
|||||||
import KasirForm from "../components/KasirForm.vue";
|
import KasirForm from "../components/KasirForm.vue";
|
||||||
import KasirTransaksiList from "../components/KasirTransaksiList.vue";
|
import KasirTransaksiList from "../components/KasirTransaksiList.vue";
|
||||||
|
|
||||||
const transaksi = ref([]);
|
const transaksi = ref({
|
||||||
|
data: [],
|
||||||
|
pagination: null
|
||||||
|
});
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const limit = 10;
|
||||||
|
|
||||||
// Fungsi untuk fetch transaksi
|
// Fetch hanya transaksi hari ini
|
||||||
const fetchTransaksi = async () => {
|
const fetchTransaksiHariIni = async (page = 1) => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await axios.get("/api/transaksi?limit=10", {
|
currentPage.value = page;
|
||||||
|
|
||||||
|
// Hanya fetch transaksi hari ini
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
limit: limit,
|
||||||
|
page: page,
|
||||||
|
start_date: today,
|
||||||
|
end_date: today
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
console.log('Fetching transaksi hari ini:', params);
|
||||||
|
|
||||||
|
const res = await axios.get(`/api/transaksi?${params}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
transaksi.value = res.data;
|
transaksi.value = {
|
||||||
console.log("Fetched transaksi:", transaksi.value);
|
data: res.data.data || [],
|
||||||
|
pagination: res.data.pagination || null
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Transaksi hari ini:", transaksi.value);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Gagal fetch transaksi:", err);
|
console.error("Gagal fetch transaksi hari ini:", err);
|
||||||
|
transaksi.value = { data: [], pagination: null };
|
||||||
|
|
||||||
|
let errorMessage = 'Gagal memuat transaksi hari ini';
|
||||||
|
if (err.response) {
|
||||||
|
errorMessage += `: ${err.response.status} - ${err.response.data?.message || err.response.statusText}`;
|
||||||
|
} else if (err.request) {
|
||||||
|
errorMessage += ': Tidak ada respon dari server';
|
||||||
|
} else {
|
||||||
|
errorMessage += `: ${err.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle pagination
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
console.log('Page changed to:', page);
|
||||||
|
|
||||||
|
if (page >= 1 && page <= (transaksi.value.pagination?.last_page || 1)) {
|
||||||
|
fetchTransaksiHariIni(page);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle transaksi baru dari KasirForm
|
||||||
|
const handleTransaksiSaved = async (newTransaksi) => {
|
||||||
|
console.log("Transaksi baru disimpan:", newTransaksi);
|
||||||
|
|
||||||
|
// Karena ini transaksi hari ini, selalu tambahkan ke list
|
||||||
|
const formattedNewTransaksi = {
|
||||||
|
id: newTransaksi.id,
|
||||||
|
kode_transaksi: newTransaksi.kode_transaksi,
|
||||||
|
created_at: newTransaksi.created_at,
|
||||||
|
total_harga: newTransaksi.total_harga || 0,
|
||||||
|
itemTransaksi: newTransaksi.itemTransaksi || [],
|
||||||
|
pendapatan: newTransaksi.total_harga || 0,
|
||||||
|
total_items: newTransaksi.itemTransaksi?.length || 0,
|
||||||
|
tanggal: new Date(newTransaksi.created_at).toLocaleDateString('id-ID')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tambahkan ke awal array
|
||||||
|
transaksi.value.data.unshift(formattedNewTransaksi);
|
||||||
|
|
||||||
|
// Update pagination
|
||||||
|
if (transaksi.value.pagination) {
|
||||||
|
transaksi.value.pagination.total += 1;
|
||||||
|
|
||||||
|
// Jika sudah penuh, hapus item terakhir
|
||||||
|
if (transaksi.value.data.length > limit) {
|
||||||
|
transaksi.value.data.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Transaksi baru ditambahkan ke list hari ini");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-refresh setiap 10 detik untuk update real-time
|
||||||
|
let refreshInterval = null;
|
||||||
|
|
||||||
|
const startAutoRefresh = () => {
|
||||||
|
if (refreshInterval) clearInterval(refreshInterval);
|
||||||
|
refreshInterval = setInterval(() => {
|
||||||
|
fetchTransaksiHariIni(currentPage.value);
|
||||||
|
}, 10000); // 10 detik
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopAutoRefresh = () => {
|
||||||
|
if (refreshInterval) {
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
refreshInterval = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchTransaksi();
|
await fetchTransaksiHariIni();
|
||||||
|
startAutoRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle ketika transaksi baru disimpan
|
// Cleanup
|
||||||
const handleTransaksiSaved = async (newTransaksi) => {
|
import { onUnmounted } from 'vue';
|
||||||
// Refresh daftar transaksi
|
onUnmounted(() => {
|
||||||
await fetchTransaksi();
|
stopAutoRefresh();
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -31,6 +31,11 @@
|
|||||||
<div v-if="activeTab === 'detail-produk'" id="detail-content" role="tabpanel">
|
<div v-if="activeTab === 'detail-produk'" id="detail-content" role="tabpanel">
|
||||||
<DetailPerProduk />
|
<DetailPerProduk />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeTab === 'riwayat'" id="riwayat-content" role="tabpanel">
|
||||||
|
<RiwayatTransaksi />
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mainLayout>
|
</mainLayout>
|
||||||
@ -42,6 +47,7 @@ import RingkasanLaporan from '../components/RingkasanLaporan.vue';
|
|||||||
import mainLayout from '../layouts/mainLayout.vue';
|
import mainLayout from '../layouts/mainLayout.vue';
|
||||||
import DetailPerNampan from '../components/DetailPerNampan.vue';
|
import DetailPerNampan from '../components/DetailPerNampan.vue';
|
||||||
import DetailPerProduk from '../components/DetailPerProduk.vue';
|
import DetailPerProduk from '../components/DetailPerProduk.vue';
|
||||||
|
import RiwayatTransaksi from '../components/RiwayatTransaksi.vue';
|
||||||
|
|
||||||
const activeTab = ref('ringkasan');
|
const activeTab = ref('ringkasan');
|
||||||
|
|
||||||
@ -49,5 +55,6 @@ const tabs = [
|
|||||||
{ name: 'Ringkasan Laporan', id: 'ringkasan' },
|
{ name: 'Ringkasan Laporan', id: 'ringkasan' },
|
||||||
{ name: 'Detail per Nampan', id: 'detail-nampan' },
|
{ name: 'Detail per Nampan', id: 'detail-nampan' },
|
||||||
{ name: 'Detail per Produk', id: 'detail-produk' },
|
{ name: 'Detail per Produk', id: 'detail-produk' },
|
||||||
|
{ name: 'Riwayat Transaksi', id: 'riwayat' },
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
Loading…
Reference in New Issue
Block a user