Kasir/resources/js/components/RingkasanLaporanB.vue
2025-09-08 09:46:04 +07:00

239 lines
11 KiB
Vue

<template>
<div class="flex flex-row items-center justify-end mt-5 gap-3">
<div class="relative w-32" ref="filterDropdownRef">
<button @click="isFilterOpen = !isFilterOpen" type="button"
class="flex items-center justify-between w-full px-3 py-2 text-sm text-left bg-C border rounded-md border-C hover:bg-C/80 focus:outline-none">
<span>{{ selectedFilterLabel }}</span>
<i class="fas fa-chevron-down"></i>
</button>
<div v-if="isFilterOpen" class="absolute z-10 w-full mt-1 bg-C border rounded-md shadow-lg border-C">
<ul class="py-1">
<li v-for="option in filterOptions" :key="option.value" @click="selectFilter(option)"
class="px-3 py-2 text-sm cursor-pointer text-D hover:bg-B">
{{ option.label }}
</li>
</ul>
</div>
</div>
<div class="relative w-40" ref="exportDropdownRef">
<button @click="isExportOpen = !isExportOpen" type="button"
class="flex items-center justify-between w-full px-3 py-2 text-sm text-left bg-C border rounded-md border-C hover:bg-C/80 focus:outline-none">
<span :class="{ 'text-D': !exportFormat }">{{ selectedExportLabel }}</span>
<i class="fas fa-chevron-down"></i>
</button>
<div v-if="isExportOpen" class="absolute z-10 w-full mt-1 bg-C border rounded-md shadow-lg border-C">
<ul class="py-1">
<li v-for="option in exportOptions" :key="option.value" @click="selectExport(option)"
class="px-3 py-2 text-sm cursor-pointer text-D hover:bg-B">
{{ option.label }}
</li>
</ul>
</div>
</div>
</div>
<div class="mt-5 overflow-x-auto">
<table class="w-full border-collapse border border-C rounded-md">
<thead>
<tr class="bg-C text-D rounded-t-md">
<th class="border-x border-C px-3 py-3">Tanggal</th>
<th class="border-x border-C px-3 py-3">Nama Sales</th>
<th class="border-x border-C px-3 py-3">Jumlah Item Terjual</th>
<th class="border-x border-C px-3 py-3">Total Berat Terjual</th>
<th class="border-x border-C px-3 py-3">Total Pendapatan</th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td colspan="5" class="p-4">
<div class="flex items-center justify-center w-full h-30">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-D"></div>
<span class="ml-2 text-gray-600">Memuat data...</span>
</div>
</td>
</tr>
<tr v-else-if="!ringkasanLaporan.length">
<td colspan="5" class="p-4 text-center">Tidak ada data untuk ditampilkan.</td>
</tr>
<template v-else v-for="item in ringkasanLaporan" :key="item.tanggal">
<template v-if="item.sales && item.sales.length > 0">
<tr class="text-center border-y border-C hover:bg-A">
<td class="px-3 py-2 border-x border-C bg-white" :rowspan="item.sales.length">{{
item.tanggal }}</td>
<td class="px-3 py-2 border-x border-C text-left">{{ item.sales[0].nama }}</td>
<td class="px-3 py-2 border-x border-C">{{ item.sales[0].item_terjual }}</td>
<td class="px-3 py-2 border-x border-C">{{ item.sales[0].berat_terjual }}</td>
<td class="flex justify-center">
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="item.sales[0].pendapatan == '-' ? 'text-center' : 'text-right'">
{{ item.sales[0].pendapatan }}
</div>
</td>
</tr>
<tr v-for="sales in item.sales.slice(1)" :key="sales.nama"
class="text-center border-y border-C hover:bg-A">
<td class="px-3 py-2 text-left border-x border-C">{{ sales.nama }}</td>
<td class="px-3 py-2 border-x border-C">{{ sales.item_terjual }}</td>
<td class="px-3 py-2 border-x border-C">{{ sales.berat_terjual }}</td>
<td class="flex justify-center">
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="sales.pendapatan == '-' ? 'text-center' : 'text-right'">
{{ sales.pendapatan }}
</div>
</td>
</tr>
<tr class="font-semibold text-center border-y border-C bg-B hover:bg-C/80">
<td class="px-3 py-2 border-x border-C" colspan="2">Total</td>
<td class="px-3 py-2 border-x border-C">{{ item.total_item_terjual }}</td>
<td class="px-3 py-2 border-x border-C">{{ item.total_berat }}</td>
<td class="flex justify-center">
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="item.sales[0].pendapatan == '-' ? 'text-center' : 'text-right'">
{{ item.total_pendapatan }}
</div>
</td>
</tr>
</template>
<template v-else>
<tr class="text-center border-y border-C hover:bg-A">
<td class="px-3 py-2 border-x border-C">{{ item.tanggal }}</td>
<td colspan="4" class="px-3 py-2 italic text-gray-500 border-x border-C">Tidak ada transaksi
pada hari ini</td>
</tr>
</template>
</template>
</tbody>
</table>
<div v-if="pagination.total > 0 && pagination.last_page > 1" class="flex items-center justify-end gap-2 mt-4">
<button @click="goToPage(pagination.current_page - 1)" :disabled="pagination.current_page === 1 || loading"
class="px-2 py-1 text-sm font-medium border rounded-md bg-C border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/80">
Sebelumnya
</button>
<span class="text-sm text-D">
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-2 py-1 text-sm font-medium border rounded-md bg-C border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/80">
Berikutnya
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, computed, nextTick } from "vue";
import axios from "axios";
// --- State ---
const isFilterOpen = ref(false);
const isExportOpen = ref(false);
const filterDropdownRef = ref(null);
const exportDropdownRef = ref(null);
const filterOptions = ref([
{ value: 'bulan', label: 'Bulanan' },
{ value: 'hari', label: 'Harian' }
]);
const exportOptions = ref([
{ value: 'pdf', label: 'Pdf' },
{ value: 'xls', label: 'Excel' },
{ value: 'csv', label: 'Csv' }
]);
const filterRingkasan = ref("bulan");
const exportFormat = ref(null);
const ringkasanLaporan = ref([]);
const loading = ref(false);
const pagination = ref({
current_page: 1,
last_page: 1,
total: 0,
});
const pendapatanWidth = ref(0);
const pendapatanElements = ref([]);
// --- Computed ---
const selectedFilterLabel = computed(() => {
return filterOptions.value.find(opt => opt.value === filterRingkasan.value)?.label;
});
const selectedExportLabel = computed(() => {
return exportOptions.value.find(opt => opt.value === exportFormat.value)?.label || 'Export Laporan';
});
const pendapatanStyle = computed(() => ({
minWidth: `${pendapatanWidth.value}px`,
padding: '0.5rem 0.75rem'
}));
// --- Watchers ---
watch(ringkasanLaporan, async (newValue) => {
if (newValue && newValue.length > 0) {
await nextTick();
let maxWidth = 0;
pendapatanElements.value.forEach(el => {
if (el && el.scrollWidth > maxWidth) {
maxWidth = el.scrollWidth;
}
});
pendapatanWidth.value = maxWidth;
}
}, { deep: true });
// --- Methods ---
const fetchRingkasan = async (page = 1) => {
loading.value = true;
pendapatanElements.value = [];
try {
const response = await axios.get(`/api/laporan?filter=${filterRingkasan.value}&page=${page}`);
ringkasanLaporan.value = response.data.data;
pagination.value = {
current_page: response.data.current_page,
last_page: response.data.last_page,
total: response.data.total,
};
} catch (error) {
console.error("Error fetching laporan:", error);
ringkasanLaporan.value = [];
} finally {
loading.value = false;
}
};
const goToPage = (page) => {
if (page >= 1 && page <= pagination.value.last_page) {
fetchRingkasan(page);
}
};
const selectFilter = (option) => {
filterRingkasan.value = option.value;
isFilterOpen.value = false;
goToPage(1);
};
const selectExport = (option) => {
exportFormat.value = option.value;
isExportOpen.value = false;
alert(`Fitur Belum dikerjakan. Laporan akan diekspor dalam format ${option.label}`);
};
const closeDropdownsOnClickOutside = (event) => {
if (filterDropdownRef.value && !filterDropdownRef.value.contains(event.target)) {
isFilterOpen.value = false;
}
if (exportDropdownRef.value && !exportDropdownRef.value.contains(event.target)) {
isExportOpen.value = false;
}
};
// --- Lifecycle Hooks ---
onMounted(() => {
fetchRingkasan(pagination.value.current_page);
document.addEventListener('click', closeDropdownsOnClickOutside);
});
onUnmounted(() => {
document.removeEventListener('click', closeDropdownsOnClickOutside);
});
</script>