223 lines
7.5 KiB
Vue
223 lines
7.5 KiB
Vue
<template>
|
|
<div class="space-y-3">
|
|
|
|
|
|
<!-- Summary Card -->
|
|
<div class="mt-3 bg-A border border-C rounded-lg p-3">
|
|
<div class="flex items-center justify-between text-sm">
|
|
<span class="text-D font-medium">Transaksi Hari Ini</span>
|
|
<span class="text-D-700 font-semibold">
|
|
Rp{{ totalPendapatan.toLocaleString() }}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center justify-between text-xs text-D mt-1">
|
|
<span>{{ transaksi.length }} transaksi</span>
|
|
<span>{{ totalItems }} item terjual</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div v-if="loading" class="flex items-center justify-center py-8">
|
|
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
|
<span class="ml-2 text-gray-600 text-sm">Memuat transaksi hari ini...</span>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div v-else-if="transaksi.length > 0" class="overflow-x-auto">
|
|
<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-C text-D 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 -->
|
|
<StrukView
|
|
:is-open="isDetailOpen"
|
|
:transaksi="selectedTransaksi"
|
|
@close="closeDetail"
|
|
/>
|
|
|
|
<!-- Loading Detail Modal -->
|
|
<div v-if="isDetailLoading" 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="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
|
<span class="text-gray-700 text-sm">Memuat detail transaksi...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
import axios from 'axios'
|
|
import StrukView from './StrukView.vue'
|
|
|
|
const props = defineProps({
|
|
transaksi: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
loading: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
pagination: {
|
|
type: Object,
|
|
default: null
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['page-change'])
|
|
|
|
// Modal state
|
|
const isDetailOpen = ref(false)
|
|
const selectedTransaksi = ref({})
|
|
const isDetailLoading = ref(false)
|
|
|
|
// 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) => {
|
|
try {
|
|
isDetailLoading.value = true
|
|
// console.log('Fetching detail untuk transaksi:', trx.kode_transaksi)
|
|
|
|
const response = await axios.get(`/api/transaksi/${trx.id}`, {
|
|
headers: {
|
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
|
},
|
|
})
|
|
|
|
// console.log('Response detail transaksi:', response.data)
|
|
selectedTransaksi.value = response.data
|
|
isDetailOpen.value = true
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching transaksi detail:', error)
|
|
|
|
let errorMessage = 'Gagal memuat detail transaksi'
|
|
if (error.response) {
|
|
errorMessage += `: ${error.response.status} - ${error.response.data?.message || error.response.statusText}`
|
|
} else if (error.request) {
|
|
errorMessage += ': Tidak ada respon dari server'
|
|
} else {
|
|
errorMessage += `: ${error.message}`
|
|
}
|
|
|
|
alert(errorMessage)
|
|
} finally {
|
|
isDetailLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Tutup modal detail
|
|
const closeDetail = () => {
|
|
isDetailOpen.value = false
|
|
selectedTransaksi.value = {}
|
|
}
|
|
</script>
|