MIF_E31221305/TA_website/resources/views/admin/bookings/index.blade.php

364 lines
21 KiB
PHP

@extends('admin.layouts.app')
@section('title', 'Pesanan')
@section('content')
<div class="p-6" x-data="bookingsData">
<!-- Header -->
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
<h2 class="text-2xl font-semibold text-gray-800">Daftar Pesanan</h2>
<!-- Filter Section -->
<div class="flex flex-wrap items-center gap-3">
<!-- Search -->
<div class="relative">
<input
type="text"
x-model="searchQuery"
@input="filterBookings()"
placeholder="Cari pesanan..."
class="w-full md:w-64 pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<svg class="w-5 h-5 text-gray-400 absolute left-3 top-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
</div>
<!-- Status Filter -->
<div class="relative" x-data="{ open: false }">
<button
@click="open = !open"
class="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<span x-text="selectedStatus ? `Status: ${selectedStatus}` : 'Semua Status'"></span>
<svg class="w-5 h-5 text-gray-400" :class="{'rotate-180': open}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div
x-show="open"
@click.away="open = false"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg z-50"
>
<div class="py-1">
<button
@click="selectedStatus = ''; filterBookings(); open = false"
class="block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
:class="{'bg-blue-50 text-blue-600': selectedStatus === ''}"
>
Semua Status
</button>
<template x-for="status in ['reservasi', 'diproses', 'selesai']" :key="status">
<button
@click="selectedStatus = status; filterBookings(); open = false"
class="block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
:class="{'bg-blue-50 text-blue-600': selectedStatus === status}"
x-text="status.charAt(0).toUpperCase() + status.slice(1)"
></button>
</template>
</div>
</div>
</div>
</div>
</div>
<!-- Bookings List -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="min-w-full">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pelanggan</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Penjahit</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tanggal</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Layanan</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pembayaran</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="bookingsTableBody">
<template x-for="booking in filteredBookings" :key="booking.id">
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900" x-text="booking.id"></td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div>
<div class="text-sm font-medium text-gray-900" x-text="booking.customer.name"></div>
<div class="text-sm text-gray-500" x-text="booking.customer.phone_number"></div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<template x-if="booking.tailor.profile_photo">
<img :src="window.assetUrl(booking.tailor.profile_photo)" class="h-10 w-10 rounded-full object-cover">
</template>
<template x-if="!booking.tailor.profile_photo">
<div class="h-10 w-10 rounded-full bg-gray-200 flex items-center justify-center">
<span class="text-gray-500 font-medium" x-text="booking.tailor.name.charAt(0)"></span>
</div>
</template>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900" x-text="booking.tailor.name"></div>
<div class="text-sm text-gray-500" x-text="booking.tailor.phone_number"></div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900" x-text="formatDate(booking.appointment_date)"></div>
<div class="text-sm text-gray-500" x-text="booking.appointment_time"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900" x-text="booking.service_type"></div>
<div class="text-sm text-gray-500" x-text="booking.category"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"
:class="{
'bg-yellow-100 text-yellow-800': booking.status === 'reservasi',
'bg-blue-100 text-blue-800': booking.status === 'diproses',
'bg-green-100 text-green-800': booking.status === 'selesai'
}"
x-text="booking.status.charAt(0).toUpperCase() + booking.status.slice(1)">
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"
:class="{
'bg-red-100 text-red-800': booking.payment_status === 'unpaid',
'bg-green-100 text-green-800': booking.payment_status === 'paid'
}"
x-text="booking.payment_status === 'paid' ? 'Lunas' : 'Belum Lunas'">
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button @click="showDetails(booking)" class="text-blue-600 hover:text-blue-900">Detail</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
<!-- Detail Modal -->
<div x-show="selectedBooking"
class="fixed inset-0 bg-black bg-opacity-50 z-50 overflow-y-auto"
x-cloak>
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg w-full max-w-2xl relative shadow-xl"
@click.away="selectedBooking = null">
<!-- Header - Fixed at top -->
<div class="sticky top-0 bg-white px-4 py-3 border-b z-10">
<div class="flex justify-between items-start">
<div>
<h3 class="text-lg font-semibold text-gray-900">Detail Pesanan #<span x-text="selectedBooking?.id"></span></h3>
<p class="mt-1 text-sm text-gray-500">Dibuat pada <span x-text="formatDate(selectedBooking?.created_at)"></span></p>
</div>
<button @click="selectedBooking = null" class="text-gray-400 hover:text-gray-500 p-1 hover:bg-gray-100 rounded-full transition-colors">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<!-- Scrollable Content -->
<div class="px-4 py-3 space-y-4">
<!-- Status Bar -->
<div class="flex items-center justify-between bg-gray-50 p-3 rounded-lg">
<div>
<span class="px-2.5 py-0.5 text-xs font-semibold rounded-full"
:class="{
'bg-yellow-100 text-yellow-800': selectedBooking?.status === 'reservasi',
'bg-blue-100 text-blue-800': selectedBooking?.status === 'diproses',
'bg-green-100 text-green-800': selectedBooking?.status === 'selesai'
}"
x-text="selectedBooking?.status.charAt(0).toUpperCase() + selectedBooking?.status.slice(1)">
</span>
</div>
<div>
<span class="px-2.5 py-0.5 text-xs font-semibold rounded-full"
:class="{
'bg-red-100 text-red-800': selectedBooking?.payment_status === 'unpaid',
'bg-green-100 text-green-800': selectedBooking?.payment_status === 'paid'
}"
x-text="selectedBooking?.payment_status === 'paid' ? 'Lunas' : 'Belum Lunas'">
</span>
</div>
</div>
<!-- Customer Info -->
<div class="bg-white rounded-lg border border-gray-200">
<div class="px-3 py-2 border-b border-gray-200 bg-gray-50">
<h4 class="text-sm font-semibold text-gray-900">Informasi Pelanggan</h4>
</div>
<div class="p-3">
<div class="space-y-3">
<div>
<p class="text-xs font-medium text-gray-500">Nama</p>
<p class="mt-0.5 text-sm text-gray-900" x-text="selectedBooking?.customer.name"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500">Telepon</p>
<p class="mt-0.5 text-sm text-gray-900" x-text="selectedBooking?.customer.phone_number"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500">Alamat</p>
<p class="mt-0.5 text-sm text-gray-900 break-words" x-text="selectedBooking?.customer.address"></p>
</div>
</div>
</div>
</div>
<!-- Order Details -->
<div class="bg-white rounded-lg border border-gray-200">
<div class="px-3 py-2 border-b border-gray-200 bg-gray-50">
<h4 class="text-sm font-semibold text-gray-900">Detail Pesanan</h4>
</div>
<div class="p-3">
<div class="space-y-3">
<div>
<p class="text-xs font-medium text-gray-500">Jenis Layanan</p>
<p class="mt-0.5 text-sm text-gray-900" x-text="selectedBooking?.service_type"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500">Kategori</p>
<p class="mt-0.5 text-sm text-gray-900" x-text="selectedBooking?.category"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500">Tanggal Appointment</p>
<p class="mt-0.5 text-sm text-gray-900" x-text="formatDate(selectedBooking?.appointment_date)"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500">Jam</p>
<p class="mt-0.5 text-sm text-gray-900" x-text="selectedBooking?.appointment_time"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500">Catatan</p>
<p class="mt-0.5 text-sm text-gray-900 break-words" x-text="selectedBooking?.notes || '-'"></p>
</div>
</div>
</div>
</div>
<!-- Design Photo -->
<div x-show="selectedBooking?.design_photo" class="bg-white rounded-lg border border-gray-200">
<div class="px-3 py-2 border-b border-gray-200 bg-gray-50">
<h4 class="text-sm font-semibold text-gray-900">Foto Design</h4>
</div>
<div class="p-3">
<img :src="window.assetUrl('storage/' + selectedBooking?.design_photo)"
class="max-h-60 w-full rounded-lg object-contain bg-gray-50"
alt="Design Photo">
</div>
</div>
<!-- Completion Info -->
<template x-if="selectedBooking?.status === 'selesai'">
<div class="bg-white rounded-lg border border-gray-200">
<div class="px-3 py-2 border-b border-gray-200 bg-gray-50">
<h4 class="text-sm font-semibold text-gray-900">Informasi Penyelesaian</h4>
</div>
<div class="p-3 space-y-4">
<div x-show="selectedBooking?.completion_photo">
<p class="text-xs font-medium text-gray-500 mb-2">Foto Hasil</p>
<img :src="window.assetUrl('storage/' + selectedBooking?.completion_photo)"
class="max-h-60 w-full rounded-lg object-contain bg-gray-50"
alt="Completion Photo">
</div>
<div x-show="selectedBooking?.completion_notes">
<p class="text-xs font-medium text-gray-500">Catatan Penyelesaian</p>
<p class="mt-0.5 text-sm text-gray-900 break-words" x-text="selectedBooking?.completion_notes"></p>
</div>
<div x-show="selectedBooking?.pickup_date">
<p class="text-xs font-medium text-gray-500">Tanggal Pengambilan</p>
<p class="mt-0.5 text-sm text-gray-900" x-text="formatDate(selectedBooking?.pickup_date)"></p>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('bookingsData', () => ({
bookings: [],
filteredBookings: [],
selectedBooking: null,
searchQuery: '',
selectedStatus: '',
init() {
this.fetchBookings();
},
async fetchBookings() {
try {
const token = '{{ session('api_token') }}';
const tokenType = '{{ session('token_type') }}';
const response = await fetch(window.apiUrl('bookings'), {
headers: {
'Accept': 'application/json',
'Authorization': `${tokenType} ${token}`
}
});
const result = await response.json();
if (result.status === 'success') {
this.bookings = result.data.data;
this.filteredBookings = this.bookings;
}
} catch (error) {
console.error('Error fetching bookings:', error);
}
},
filterBookings() {
this.filteredBookings = this.bookings.filter(booking => {
const matchesSearch = this.searchQuery === '' ||
booking.customer.name.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
booking.tailor.name.toLowerCase().includes(this.searchQuery.toLowerCase());
const matchesStatus = this.selectedStatus === '' ||
booking.status === this.selectedStatus;
return matchesSearch && matchesStatus;
});
},
showDetails(booking) {
this.selectedBooking = booking;
},
formatDate(dateString) {
if (!dateString) return '-';
const options = { year: 'numeric', month: 'long', day: 'numeric' };
return new Date(dateString).toLocaleDateString('id-ID', options);
}
}));
});
</script>
@endpush