[Update] Search bersadarkan nama

This commit is contained in:
Baghaztra 2025-09-24 15:50:22 +07:00
parent be38f618a0
commit dac6f59018
2 changed files with 59 additions and 106 deletions

View File

@ -12,13 +12,13 @@ use Illuminate\Support\Facades\DB;
class TransaksiController extends Controller class TransaksiController extends Controller
{ {
// List semua transaksi
public function index(Request $request) public function index(Request $request)
{ {
$limit = $request->query('limit', 10); $limit = $request->query('limit', 10);
$page = $request->query('page', 1); $page = $request->query('page', 1);
$startDate = $request->query('start_date'); $startDate = $request->query('start_date');
$endDate = $request->query('end_date'); $endDate = $request->query('end_date');
$search = $request->query('search');
$query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.produk']); $query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.produk']);
@ -35,10 +35,15 @@ class TransaksiController extends Controller
$query->whereDate('created_at', $today); $query->whereDate('created_at', $today);
} }
// Order by latest // Search berdasarkan kode transaksi atau nama pelanggan
$query->latest(); if ($search) {
$query->where(function ($q) use ($search) {
$q->where('kode_transaksi', 'like', '%' . $search . '%')
->orWhere('nama_pembeli', 'like', '%' . $search . '%');
});
}
// Pagination $query->latest();
$transaksi = $query->paginate($limit, ['*'], 'page', $page); $transaksi = $query->paginate($limit, ['*'], 'page', $page);
// Transform data // Transform data

View File

@ -4,37 +4,22 @@
<hr class="border-B mb-5" /> <hr class="border-B mb-5" />
<!-- Filter Section --> <!-- Filter Section -->
<div class="flex flex-col lg:flex-row my-3 gap-3 lg:gap-5"> <div class="flex flex-col md:flex-row justify-between my-3 gap-3 md:gap-5">
<!-- Date Range Filter --> <!-- Date Range Filter -->
<div class="w-full lg:w-1/3"> <div class="w-full md:w-1/3">
<DatePicker <DatePicker v-model="dateRange" label="Filter Tanggal" placeholder="Pilih rentang tanggal" :max-days="31"
v-model="dateRange" @change="handleDateChange" />
label="Filter Tanggal"
placeholder="Pilih rentang tanggal"
:max-days="31"
@change="handleDateChange"
/>
</div> </div>
<!-- Search Section - Improved Responsiveness --> <div class="flex flex-col sm:flex-row w-full md:w-1/3">
<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"> <div class="flex-1 min-w-0">
<InputField <input placeholder="Cari kode transaksi atau nama pembeli" v-model="searchQuery"
placeholder="Cari kode transaksi..." class="mt-1 block w-full rounded-l-md shadow-sm sm:text-sm bg-A text-D border-B focus:border-C focus:ring focus:outline-none focus:ring-D focus:ring-opacity-50 p-2" />
v-model="searchQuery"
class="w-full"
@input="handleSearch"
/>
</div> </div>
<div>
<!-- Reset Button --> <button @click="handleSearch"
<div class="flex-shrink-0"> class="mt-1 px-4 py-2 bg-C hover:bg-C/80 text-D rounded-r-md text-sm font-medium transition-colors">
<button Cari
@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> </button>
</div> </div>
</div> </div>
@ -61,10 +46,10 @@
</button> </button>
</th> </th>
<th class="border-x border-C px-3 py-3 text-left"> <th class="border-x border-C px-3 py-3 text-left">
<button @click="handleSort('total_harga')" <button @click="handleSort('nama_pembeli')"
class="flex items-center justify-between w-full hover:text-D/80 transition-colors"> class="flex items-center justify-between w-full hover:text-D/80 transition-colors">
<span>Nama Pembeli</span> <span>Nama Pembeli</span>
<i :class="getSortIcon('total_harga')" class="ml-2"></i> <i :class="getSortIcon('nama_pembeli')" class="ml-2"></i>
</button> </button>
</th> </th>
<th class="border-x border-C px-3 py-3 text-left"> <th class="border-x border-C px-3 py-3 text-left">
@ -82,7 +67,7 @@
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-C/20"> <tbody class="divide-y divide-C/20">
<!-- Loading Row --> <!-- Loading Row -->
<tr v-if="loading"> <tr v-if="loading">
@ -99,7 +84,7 @@
<td :colspan="tableColumns" class="p-12 text-center"> <td :colspan="tableColumns" class="p-12 text-center">
<div class="text-D/50 space-y-2"> <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"> <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" <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" /> 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> </svg>
<div class="space-y-1"> <div class="space-y-1">
@ -122,32 +107,31 @@
<!-- Kode Transaksi --> <!-- Kode Transaksi -->
<td class="text-sm border-x border-C px-3 py-3"> <td class="text-sm border-x border-C px-3 py-3">
{{ trx.kode_transaksi }} {{ trx.kode_transaksi }}
</td> </td>
<!-- Nama pembeli -->
<td class="text-sm border-x border-C px-3 py-3"> <td class="text-sm border-x border-C px-3 py-3">
{{ trx.nama_pembeli }} {{ trx.nama_pembeli || '-' }}
</td> </td>
<!-- Total --> <!-- Total -->
<td class="text-sm border-x border-C px-3 py-3 text-center"> <td class="text-sm border-x border-C px-3 py-3 text-center">
Rp{{ (trx.total_harga || 0).toLocaleString() }} Rp{{ (trx.total_harga || 0).toLocaleString('id-ID') }}
</td> </td>
<!-- Jumlah Item --> <!-- Jumlah Item -->
<td class="text-sm border-x border-C px-3 py-3 text-center"> <td class="text-sm border-x border-C px-3 py-3 text-center">
{{ trx.total_items || 0 }} {{ trx.total_items || 0 }}
</td> </td>
<!-- Aksi --> <!-- Aksi -->
<td class="border-r border-C px-3 py-3 text-center"> <td class="border-r border-C px-3 py-3 text-center">
<button <button @click="lihatDetail(trx)"
@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" 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" :disabled="isDetailLoading">
> <i v-if="isDetailLoading && selectedTransaksi.id === trx.id"
<i v-if="isDetailLoading && selectedTransaksi.id === trx.id" class="fas fa-spinner fa-spin mr-1"></i>
class="fas fa-spinner fa-spin mr-1"></i>
<span>Lihat Detail</span> <span>Lihat Detail</span>
</button> </button>
</td> </td>
@ -159,34 +143,29 @@
</div> </div>
<!-- Pagination --> <!-- Pagination -->
<div v-if="pagination && pagination.total > 0 && pagination.last_page > 1" <div v-if="pagination && pagination.total > 0 && pagination.last_page > 1"
class="flex items-center justify-between gap-4 mt-6 px-1"> class="flex items-center justify-between gap-4 mt-6 px-1">
<div class="text-sm text-D/70"> <div class="text-sm text-D/70">
Menampilkan {{ pagination.from }} - {{ pagination.to }} dari {{ pagination.total }} transaksi Menampilkan {{ pagination.from }} - {{ pagination.to }} dari {{ pagination.total }} transaksi
<span v-if="filteredTransaksi.length !== pagination.per_page" class="ml-2 text-blue-600"> <span v-if="filteredTransaksi.length !== pagination.per_page" class="ml-2 text-blue-600">
({{ filteredTransaksi.length }} sesuai filter) ({{ filteredTransaksi.length }} sesuai filter)
</span> </span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button @click="goToPage(pagination.current_page - 1)" :disabled="pagination.current_page === 1 || loading"
@click="goToPage(pagination.current_page - 1)" 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">
: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> <i class="fas fa-chevron-left mr-1"></i>
Sebelumnya Sebelumnya
</button> </button>
<span class="text-sm text-D/70 px-3"> <span class="text-sm text-D/70 px-3">
Halaman {{ pagination.current_page }} dari {{ pagination.last_page }} Halaman {{ pagination.current_page }} dari {{ pagination.last_page }}
</span> </span>
<button <button @click="goToPage(pagination.current_page + 1)"
@click="goToPage(pagination.current_page + 1)"
:disabled="pagination.current_page === pagination.last_page || loading" :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" 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 Berikutnya
<i class="fas fa-chevron-right ml-1"></i> <i class="fas fa-chevron-right ml-1"></i>
</button> </button>
@ -194,11 +173,7 @@
</div> </div>
<!-- Modal Detail Transaksi --> <!-- Modal Detail Transaksi -->
<StrukView <StrukView :is-open="isDetailOpen" :transaksi="selectedTransaksi" @close="closeDetail" />
:is-open="isDetailOpen"
:transaksi="selectedTransaksi"
@close="closeDetail"
/>
<!-- Loading Overlay for Detail --> <!-- 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 v-if="isDetailLoading" class="fixed inset-0 bg-black/60 flex items-center justify-center z-[9999] p-4">
@ -214,7 +189,6 @@
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import DatePicker from '@/components/DatePicker.vue' import DatePicker from '@/components/DatePicker.vue'
import InputField from '@/components/InputField.vue'
import StrukView from '@/components/StrukView.vue' import StrukView from '@/components/StrukView.vue'
// Props & Emits // Props & Emits
@ -251,38 +225,29 @@ const selectedTransaksi = ref({})
// Computed // Computed
const filteredTransaksi = computed(() => { const filteredTransaksi = computed(() => {
let filtered = [...transaksi.value] let filtered = [...transaksi.value]
// Date filter
if (dateRange.value.start && dateRange.value.end) { if (dateRange.value.start && dateRange.value.end) {
const startDate = new Date(dateRange.value.start) const startDate = new Date(dateRange.value.start)
const endDate = new Date(dateRange.value.end) const endDate = new Date(dateRange.value.end)
endDate.setHours(23, 59, 59, 999) endDate.setHours(23, 59, 59, 999)
filtered = filtered.filter(trx => { filtered = filtered.filter(trx => {
const trxDate = new Date(trx.created_at) const trxDate = new Date(trx.created_at)
return trxDate >= startDate && trxDate <= endDate return trxDate >= startDate && trxDate <= endDate
}) })
} }
// Status filter // Status filter
if (statusDipilih.value) { if (statusDipilih.value) {
filtered = filtered.filter(trx => trx.status === statusDipilih.value) filtered = filtered.filter(trx => trx.status === statusDipilih.value)
} }
// Payment method filter // Payment method filter
if (pembayaranDipilih.value) { if (pembayaranDipilih.value) {
filtered = filtered.filter(trx => trx.metode_pembayaran === pembayaranDipilih.value) filtered = filtered.filter(trx => trx.metode_pembayaran === pembayaranDipilih.value)
} }
// Search filter // Removed searchQuery filter to prevent client-side filtering
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 return filtered
}) })
@ -290,26 +255,26 @@ const sortedTransaksi = computed(() => {
return [...filteredTransaksi.value].sort((a, b) => { return [...filteredTransaksi.value].sort((a, b) => {
let aVal = a[sortField.value] || '' let aVal = a[sortField.value] || ''
let bVal = b[sortField.value] || '' let bVal = b[sortField.value] || ''
// Handle numeric fields // Handle numeric fields
if (['total_harga', 'total_items'].includes(sortField.value)) { if (['total_harga', 'total_items'].includes(sortField.value)) {
aVal = parseFloat(aVal) || 0 aVal = parseFloat(aVal) || 0
bVal = parseFloat(bVal) || 0 bVal = parseFloat(bVal) || 0
} }
if (aVal < bVal) return sortDirection.value === 'asc' ? -1 : 1 if (aVal < bVal) return sortDirection.value === 'asc' ? -1 : 1
if (aVal > bVal) return sortDirection.value === 'asc' ? 1 : -1 if (aVal > bVal) return sortDirection.value === 'asc' ? 1 : -1
return 0 return 0
}) })
}) })
const tableColumns = computed(() => 7) const tableColumns = computed(() => 6)
// Methods // Methods
const fetchTransaksi = async (page = 1) => { const fetchTransaksi = async (page = 1) => {
try { try {
loading.value = true loading.value = true
const params = new URLSearchParams({ const params = new URLSearchParams({
page, page,
limit: 10, limit: 10,
@ -328,12 +293,9 @@ const fetchTransaksi = async (page = 1) => {
transaksi.value = response.data.data || [] transaksi.value = response.data.data || []
pagination.value = response.data.pagination || null pagination.value = response.data.pagination || null
console.log("trans:", transaksi.value); console.log("data", transaksi.value)
} catch (error) { } catch (error) {
console.error('Error fetching transaksi:', error) console.error('Error fetching transaksi:', error)
alert('Gagal memuat data transaksi: ' + (error.response?.data?.message || error.message))
transaksi.value = [] transaksi.value = []
} finally { } finally {
loading.value = false loading.value = false
@ -347,12 +309,8 @@ const handleDateChange = (newRange) => {
} }
const handleSearch = () => { const handleSearch = () => {
// Debounce search (optional) pagination.value = null
clearTimeout(window.searchTimeout) fetchTransaksi(1)
window.searchTimeout = setTimeout(() => {
pagination.value = null
fetchTransaksi(1)
}, 300)
} }
const handleSort = (field) => { const handleSort = (field) => {
@ -362,13 +320,13 @@ const handleSort = (field) => {
sortField.value = field sortField.value = field
sortDirection.value = 'asc' sortDirection.value = 'asc'
} }
fetchTransaksi(1) fetchTransaksi(1)
} }
const getSortIcon = (field) => { const getSortIcon = (field) => {
if (sortField.value !== field) return 'fas fa-sort text-D/40' if (sortField.value !== field) return 'fas fa-sort text-D/40'
if (sortDirection.value === 'asc') return 'fas fa-sort-up text-D' if (sortDirection.value === 'asc') return 'fas fa-sort-up text-D'
return 'fas fa-sort-down text-D' return 'fas fa-sort-down text-D'
} }
@ -383,7 +341,7 @@ const lihatDetail = async (trx) => {
try { try {
isDetailLoading.value = true isDetailLoading.value = true
selectedTransaksi.value = trx // Show loading state first selectedTransaksi.value = trx // Show loading state first
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")}`
@ -395,7 +353,6 @@ const lihatDetail = async (trx) => {
total_items: response.data.itemTransaksi?.length || 0 total_items: response.data.itemTransaksi?.length || 0
} }
isDetailOpen.value = true isDetailOpen.value = true
} catch (error) { } catch (error) {
console.error('Error fetching detail:', error) console.error('Error fetching detail:', error)
alert('Gagal memuat detail transaksi: ' + (error.response?.data?.message || error.message)) alert('Gagal memuat detail transaksi: ' + (error.response?.data?.message || error.message))
@ -409,15 +366,6 @@ const closeDetail = () => {
selectedTransaksi.value = {} 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 // Helper Functions
const formatDate = (dateString) => { const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('id-ID', { return new Date(dateString).toLocaleDateString('id-ID', {