feat(admin): tambah fitur zoom bukti pembayaran pada detail order tiket dan booking
|
|
@ -102,7 +102,7 @@ public function uploadProof(Request $request, string $code)
|
||||||
'payment_proof' => ['required', 'file', 'mimes:jpg,jpeg,png,pdf', 'max:5120'],
|
'payment_proof' => ['required', 'file', 'mimes:jpg,jpeg,png,pdf', 'max:5120'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$directory = public_path('payment-proofs');
|
$directory = storage_path('payment-proofs');
|
||||||
if (! is_dir($directory)) {
|
if (! is_dir($directory)) {
|
||||||
mkdir($directory, 0755, true);
|
mkdir($directory, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ public function order(Request $request)
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$qrDirectory = public_path('qrcodes');
|
$qrDirectory = storage_path('qrcodes');
|
||||||
if (! is_dir($qrDirectory)) {
|
if (! is_dir($qrDirectory)) {
|
||||||
mkdir($qrDirectory, 0755, true);
|
mkdir($qrDirectory, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +88,7 @@ public function order(Request $request)
|
||||||
$ticketCode = generateUniqueCode('AT', TicketOrderItem::class, 'ticket_code');
|
$ticketCode = generateUniqueCode('AT', TicketOrderItem::class, 'ticket_code');
|
||||||
$qrPath = 'qrcodes/'.$ticketCode.'.svg';
|
$qrPath = 'qrcodes/'.$ticketCode.'.svg';
|
||||||
$qrImage = QrCode::format('svg')->size(320)->generate($ticketCode);
|
$qrImage = QrCode::format('svg')->size(320)->generate($ticketCode);
|
||||||
file_put_contents(public_path($qrPath), $qrImage);
|
file_put_contents(storage_path($qrPath), $qrImage);
|
||||||
|
|
||||||
TicketOrderItem::create([
|
TicketOrderItem::create([
|
||||||
'ticket_order_id' => $order->id,
|
'ticket_order_id' => $order->id,
|
||||||
|
|
@ -130,7 +130,7 @@ public function uploadProof(Request $request, string $code)
|
||||||
'payment_proof' => ['required', 'file', 'mimes:jpg,jpeg,png,pdf', 'max:5120'],
|
'payment_proof' => ['required', 'file', 'mimes:jpg,jpeg,png,pdf', 'max:5120'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$directory = public_path('payment-proofs');
|
$directory = storage_path('payment-proofs');
|
||||||
if (! is_dir($directory)) {
|
if (! is_dir($directory)) {
|
||||||
mkdir($directory, 0755, true);
|
mkdir($directory, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,11 @@ function generateUniqueCode(string $prefix, string $modelClass, string $column =
|
||||||
|
|
||||||
return $code;
|
return $code;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! function_exists('storage_url')) {
|
||||||
|
function storage_url(string $path): string
|
||||||
|
{
|
||||||
|
return route('storage.show', ['path' => $path]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@extends('layouts.admin')
|
@extends('layouts.admin')
|
||||||
@section('title', 'Detail Booking')
|
@section('title', 'Detail Booking')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
@ -107,14 +107,6 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($booking->payment_proof)
|
|
||||||
@php($proofPath = str_starts_with($booking->payment_proof, 'payment-proofs/') ? $booking->payment_proof : 'payment-proofs/'.$booking->payment_proof)
|
|
||||||
<hr class="my-4" style="border-color: var(--teal-100);">
|
|
||||||
<a href="{{ asset($proofPath) }}" target="_blank" class="btn btn-outline-primary">
|
|
||||||
<i class="fas fa-image me-2"></i>Lihat Bukti Pembayaran
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -171,7 +163,69 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if($booking->payment_proof)
|
||||||
|
@php($proofPath = str_starts_with($booking->payment_proof, 'payment-proofs/') ? $booking->payment_proof : 'payment-proofs/'.$booking->payment_proof)
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="mb-0" style="font-weight: 700;">
|
||||||
|
<i class="fas fa-image me-2"></i>Bukti Pembayaran
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center bg-light rounded-bottom p-4">
|
||||||
|
<div class="proof-container position-relative d-inline-block">
|
||||||
|
<img src="{{ storage_url($proofPath) }}"
|
||||||
|
alt="Bukti Pembayaran"
|
||||||
|
class="img-fluid rounded shadow-sm cursor-pointer"
|
||||||
|
style="max-height: 300px; border: 4px solid white; transition: transform 0.2s;"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#proofModal"
|
||||||
|
onmouseover="this.style.transform='scale(1.02)'"
|
||||||
|
onmouseout="this.style.transform='scale(1)'">
|
||||||
|
<div class="mt-3">
|
||||||
|
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#proofModal">
|
||||||
|
<i class="fas fa-search-plus me-1"></i>Perbesar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@section('modals')
|
||||||
|
@if($booking->payment_proof)
|
||||||
|
@php($proofPath = str_starts_with($booking->payment_proof, 'payment-proofs/') ? $booking->payment_proof : 'payment-proofs/'.$booking->payment_proof)
|
||||||
|
<!-- Payment Proof Zoom Modal -->
|
||||||
|
<div class="modal fade" id="proofModal" tabindex="-1" aria-labelledby="proofModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||||
|
<div class="modal-content" style="border-radius: 1rem; overflow: hidden; border: none; max-height: 90vh;">
|
||||||
|
<div class="modal-header border-0 bg-light">
|
||||||
|
<h5 class="modal-title fw-bold" id="proofModalLabel">Detail Bukti Pembayaran</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-0 bg-dark text-center d-flex align-items-center justify-content-center" style="overflow: hidden;">
|
||||||
|
<img src="{{ storage_url($proofPath) }}" alt="Bukti Pembayaran Full" class="img-fluid" style="max-height: 70vh; object-fit: contain;">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 bg-light">
|
||||||
|
<a href="{{ storage_url($proofPath) }}" download class="btn btn-primary btn-sm">
|
||||||
|
<i class="fas fa-download me-2"></i>Download Gambar
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Tutup</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
|
||||||
|
|
@ -93,14 +93,36 @@
|
||||||
|
|
||||||
@if($order->payment_proof)
|
@if($order->payment_proof)
|
||||||
@php($proofPath = str_starts_with($order->payment_proof, 'payment-proofs/') ? $order->payment_proof : 'payment-proofs/'.$order->payment_proof)
|
@php($proofPath = str_starts_with($order->payment_proof, 'payment-proofs/') ? $order->payment_proof : 'payment-proofs/'.$order->payment_proof)
|
||||||
<hr class="my-4" style="border-color: var(--teal-100);">
|
|
||||||
<a href="{{ asset($proofPath) }}" target="_blank" class="btn btn-outline-primary">
|
|
||||||
<i class="fas fa-image me-2"></i>Lihat Bukti Pembayaran
|
|
||||||
</a>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if($order->payment_proof)
|
||||||
|
<!-- Payment Proof Card -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="mb-0" style="font-weight: 700;">
|
||||||
|
<i class="fas fa-file-invoice-dollar me-2"></i>Bukti Pembayaran
|
||||||
|
</h6>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#proofModal">
|
||||||
|
<i class="fas fa-search-plus me-1"></i>Perbesar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center p-0 overflow-hidden" style="background: #f8fafc;">
|
||||||
|
<div class="p-4">
|
||||||
|
<img src="{{ storage_url($proofPath) }}"
|
||||||
|
alt="Bukti Pembayaran"
|
||||||
|
class="img-fluid rounded shadow-sm border cursor-pointer"
|
||||||
|
style="max-height: 400px; transition: transform 0.3s ease;"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#proofModal"
|
||||||
|
onmouseover="this.style.transform='scale(1.02)'"
|
||||||
|
onmouseout="this.style.transform='scale(1)'">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<!-- Ticket Items Card -->
|
<!-- Ticket Items Card -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
@ -191,3 +213,36 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@section('modals')
|
||||||
|
@if($order->payment_proof)
|
||||||
|
<!-- Payment Proof Zoom Modal -->
|
||||||
|
<div class="modal fade" id="proofModal" tabindex="-1" aria-labelledby="proofModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||||
|
<div class="modal-content" style="border-radius: 1rem; overflow: hidden; border: none; max-height: 90vh;">
|
||||||
|
<div class="modal-header border-0 bg-light">
|
||||||
|
<h5 class="modal-title fw-bold" id="proofModalLabel">Detail Bukti Pembayaran</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-0 bg-dark text-center d-flex align-items-center justify-content-center" style="overflow: hidden;">
|
||||||
|
<img src="{{ storage_url($proofPath) }}" alt="Bukti Pembayaran Full" class="img-fluid" style="max-height: 70vh; object-fit: contain;">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 bg-light">
|
||||||
|
<a href="{{ storage_url($proofPath) }}" download class="btn btn-primary btn-sm">
|
||||||
|
<i class="fas fa-download me-2"></i>Download Gambar
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Tutup</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,7 @@ function initPasswordToggles() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@yield('modals')
|
||||||
@stack('scripts')
|
@stack('scripts')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -268,8 +268,8 @@
|
||||||
<div class="qr-section">
|
<div class="qr-section">
|
||||||
<div class="qr-label">Scan untuk verifikasi</div>
|
<div class="qr-label">Scan untuk verifikasi</div>
|
||||||
<div class="qr-code-wrapper">
|
<div class="qr-code-wrapper">
|
||||||
@if(file_exists(public_path($item->qr_code_path)))
|
@if(file_exists(storage_path($item->qr_code_path)))
|
||||||
<img src="{{ public_path($item->qr_code_path) }}" style="width:180px; height:180px; display:block;" alt="QR Code">
|
<img src="{{ storage_path($item->qr_code_path) }}" style="width:180px; height:180px; display:block;" alt="QR Code">
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="ticket-code-text">{{ $item->ticket_code }}</div>
|
<div class="ticket-code-text">{{ $item->ticket_code }}</div>
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,8 @@
|
||||||
<div class="ticket-qr-section">
|
<div class="ticket-qr-section">
|
||||||
<div class="qr-label">Scan untuk verifikasi</div>
|
<div class="qr-label">Scan untuk verifikasi</div>
|
||||||
<div class="qr-wrapper">
|
<div class="qr-wrapper">
|
||||||
@if(file_exists(public_path($item->qr_code_path)))
|
@if(file_exists(storage_path($item->qr_code_path)))
|
||||||
<img src="{{ asset($item->qr_code_path) }}" style="width: 130px; height: 130px; display: block;" alt="QR Code">
|
<img src="{{ storage_url($item->qr_code_path) }}" style="width: 130px; height: 130px; display: block;" alt="QR Code">
|
||||||
@else
|
@else
|
||||||
<div style="width: 130px; height: 130px; background: #e5e7eb; display: flex; align-items: center; justify-content: center; border-radius: 8px;">
|
<div style="width: 130px; height: 130px; background: #e5e7eb; display: flex; align-items: center; justify-content: center; border-radius: 8px;">
|
||||||
<i class="fas fa-qrcode" style="font-size: 50px; color: #9ca3af;"></i>
|
<i class="fas fa-qrcode" style="font-size: 50px; color: #9ca3af;"></i>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,18 @@
|
||||||
use App\Models\Place;
|
use App\Models\Place;
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Facades\Response;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
Route::get('/storage-file/{path}', function ($path) {
|
||||||
|
$fullPath = storage_path($path);
|
||||||
|
|
||||||
|
if (!File::exists($fullPath)) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::file($fullPath);
|
||||||
|
})->where('path', '.*')->name('storage.show');
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
$tickets = Ticket::where('is_active', true)
|
$tickets = Ticket::where('is_active', true)
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 306 KiB |
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320" viewBox="0 0 320 320"><rect x="0" y="0" width="320" height="320" fill="#ffffff"/><g transform="scale(15.238)"><g transform="translate(0,0)"><path fill-rule="evenodd" d="M8 0L8 1L9 1L9 2L8 2L8 5L11 5L11 4L12 4L12 3L13 3L13 0L12 0L12 3L11 3L11 2L10 2L10 0ZM9 2L9 4L11 4L11 3L10 3L10 2ZM8 6L8 7L9 7L9 8L8 8L8 9L7 9L7 8L6 8L6 9L4 9L4 8L0 8L0 9L1 9L1 10L0 10L0 11L1 11L1 13L2 13L2 11L3 11L3 13L4 13L4 12L5 12L5 13L7 13L7 12L8 12L8 14L9 14L9 16L10 16L10 14L11 14L11 13L12 13L12 12L13 12L13 15L11 15L11 17L8 17L8 21L10 21L10 20L11 20L11 21L13 21L13 19L14 19L14 20L15 20L15 21L17 21L17 20L18 20L18 21L20 21L20 20L19 20L19 18L20 18L20 17L21 17L21 16L17 16L17 18L15 18L15 19L14 19L14 17L15 17L15 16L16 16L16 15L17 15L17 14L19 14L19 15L20 15L20 14L21 14L21 12L19 12L19 13L18 13L18 11L20 11L20 10L21 10L21 8L20 8L20 9L19 9L19 8L16 8L16 9L15 9L15 10L13 10L13 9L14 9L14 8L13 8L13 9L12 9L12 12L11 12L11 13L10 13L10 12L9 12L9 11L11 11L11 6L10 6L10 7L9 7L9 6ZM12 6L12 7L13 7L13 6ZM9 8L9 10L8 10L8 11L9 11L9 10L10 10L10 8ZM3 9L3 11L4 11L4 9ZM6 9L6 10L7 10L7 9ZM16 9L16 10L17 10L17 9ZM18 9L18 10L19 10L19 9ZM6 11L6 12L7 12L7 11ZM14 11L14 14L15 14L15 15L16 15L16 14L17 14L17 13L16 13L16 12L17 12L17 11ZM12 16L12 17L11 17L11 18L10 18L10 19L11 19L11 18L12 18L12 19L13 19L13 17L14 17L14 16ZM18 17L18 18L19 18L19 17ZM0 0L0 7L7 7L7 0ZM1 1L1 6L6 6L6 1ZM2 2L2 5L5 5L5 2ZM14 0L14 7L21 7L21 0ZM15 1L15 6L20 6L20 1ZM16 2L16 5L19 5L19 2ZM0 14L0 21L7 21L7 14ZM1 15L1 20L6 20L6 15ZM2 16L2 19L5 19L5 16Z" fill="#000000"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |