Kasir/resources/js/components/KasirTransaksiList.vue
2025-09-29 09:35:58 +07:00

222 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-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 -->
<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>