Admin bisa tutup buka venue
This commit is contained in:
parent
6873f94b83
commit
cbbd272b15
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Models\Venue;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class ReopenVenuesCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'venues:reopen';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Automatically reopen venues that have reached their reopen date';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->info('Checking for venues to reopen...');
|
||||||
|
|
||||||
|
$venuesReopened = 0;
|
||||||
|
|
||||||
|
// Get all closed venues that should be reopened today
|
||||||
|
$venues = Venue::where('status', 'close')
|
||||||
|
->whereNotNull('reopen_date')
|
||||||
|
->whereDate('reopen_date', '<=', Carbon::today())
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($venues as $venue) {
|
||||||
|
if ($venue->checkAutoReopen()) {
|
||||||
|
$this->info("Venue '{$venue->name}' has been automatically reopened.");
|
||||||
|
$venuesReopened++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($venuesReopened > 0) {
|
||||||
|
$this->info("Successfully reopened {$venuesReopened} venue(s).");
|
||||||
|
} else {
|
||||||
|
$this->info('No venues to reopen today.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,11 @@ class Kernel extends ConsoleKernel
|
||||||
*/
|
*/
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
// $schedule->command('inspire')->hourly();
|
// Run venue reopen check every day at 00:01
|
||||||
|
$schedule->command('venues:reopen')
|
||||||
|
->dailyAt('00:01')
|
||||||
|
->withoutOverlapping()
|
||||||
|
->runInBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class VenueController extends Controller
|
class VenueController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -22,6 +23,11 @@ public function index()
|
||||||
return redirect()->route('admin.dashboard')->with('error', 'Anda belum memiliki venue yang ditugaskan.');
|
return redirect()->route('admin.dashboard')->with('error', 'Anda belum memiliki venue yang ditugaskan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for auto reopen
|
||||||
|
if ($venue->checkAutoReopen()) {
|
||||||
|
session()->flash('success', 'Venue telah dibuka kembali secara otomatis!');
|
||||||
|
}
|
||||||
|
|
||||||
return view('admin.venues.index', compact('venue'));
|
return view('admin.venues.index', compact('venue'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,16 +97,27 @@ public function update(Request $request)
|
||||||
$imagePath = $request->file('image')->store('venues', 'public');
|
$imagePath = $request->file('image')->store('venues', 'public');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update venue data
|
// Prepare update data
|
||||||
$venue->update([
|
$updateData = [
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
'address' => $request->address,
|
'address' => $request->address,
|
||||||
'phone' => $request->phone,
|
'phone' => $request->phone,
|
||||||
'description' => $request->description,
|
'description' => $request->description,
|
||||||
'open_time' => $request->open_time,
|
|
||||||
'close_time' => $request->close_time,
|
|
||||||
'image' => $imagePath,
|
'image' => $imagePath,
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
// Only update operating hours if venue is open
|
||||||
|
if ($venue->status === 'open') {
|
||||||
|
$updateData['open_time'] = $request->open_time;
|
||||||
|
$updateData['close_time'] = $request->close_time;
|
||||||
|
} else {
|
||||||
|
// If venue is closed, update original times
|
||||||
|
$updateData['original_open_time'] = $request->open_time;
|
||||||
|
$updateData['original_close_time'] = $request->close_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update venue data
|
||||||
|
$venue->update($updateData);
|
||||||
|
|
||||||
return redirect()->route('admin.venue.index')
|
return redirect()->route('admin.venue.index')
|
||||||
->with('success', 'Informasi venue berhasil diperbarui!');
|
->with('success', 'Informasi venue berhasil diperbarui!');
|
||||||
|
@ -111,4 +128,63 @@ public function update(Request $request)
|
||||||
->withInput();
|
->withInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle venue status (open/close)
|
||||||
|
*/
|
||||||
|
public function toggleStatus(Request $request)
|
||||||
|
{
|
||||||
|
$venue = auth()->user()->venue;
|
||||||
|
|
||||||
|
if (!$venue) {
|
||||||
|
return response()->json(['error' => 'Venue tidak ditemukan'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($venue->status === 'open') {
|
||||||
|
// Closing venue - validate required fields
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'close_reason' => 'required|string|max:500',
|
||||||
|
'reopen_date' => 'required|date|after:today',
|
||||||
|
], [
|
||||||
|
'close_reason.required' => 'Alasan penutupan harus diisi.',
|
||||||
|
'reopen_date.required' => 'Tanggal buka kembali harus diisi.',
|
||||||
|
'reopen_date.date' => 'Format tanggal tidak valid.',
|
||||||
|
'reopen_date.after' => 'Tanggal buka kembali harus setelah hari ini.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'Validasi gagal',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$venue->closeVenue($request->close_reason, $request->reopen_date);
|
||||||
|
$message = 'Venue berhasil ditutup!';
|
||||||
|
} else {
|
||||||
|
// Opening venue
|
||||||
|
$venue->openVenue();
|
||||||
|
$message = 'Venue berhasil dibuka!';
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => $message,
|
||||||
|
'status' => $venue->status,
|
||||||
|
'venue' => [
|
||||||
|
'status' => $venue->status,
|
||||||
|
'close_reason' => $venue->close_reason,
|
||||||
|
'reopen_date' => $venue->reopen_date ? $venue->reopen_date->format('d M Y') : null,
|
||||||
|
'open_time' => $venue->open_time ? Carbon::parse($venue->open_time)->format('H:i') : null,
|
||||||
|
'close_time' => $venue->close_time ? Carbon::parse($venue->close_time)->format('H:i') : null,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'Terjadi kesalahan: ' . $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class Venue extends Model
|
class Venue extends Model
|
||||||
{
|
{
|
||||||
|
@ -13,15 +14,79 @@ class Venue extends Model
|
||||||
'name',
|
'name',
|
||||||
'address',
|
'address',
|
||||||
'image',
|
'image',
|
||||||
'phone', // Pastikan field ini ada
|
'phone',
|
||||||
'description', // Pastikan field ini ada
|
'description',
|
||||||
'open_time', // Pastikan field ini ada
|
'open_time',
|
||||||
'close_time', // Pastikan field ini ada
|
'close_time',
|
||||||
|
'status',
|
||||||
|
'close_reason',
|
||||||
|
'reopen_date',
|
||||||
|
'original_open_time',
|
||||||
|
'original_close_time',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $dates = [
|
||||||
|
'reopen_date',
|
||||||
|
];
|
||||||
|
|
||||||
public function tables()
|
public function tables()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Table::class);
|
return $this->hasMany(Table::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if venue should automatically reopen
|
||||||
|
*/
|
||||||
|
public function checkAutoReopen()
|
||||||
|
{
|
||||||
|
if ($this->status === 'close' && $this->reopen_date && Carbon::today()->gte($this->reopen_date)) {
|
||||||
|
$this->update([
|
||||||
|
'status' => 'open',
|
||||||
|
'open_time' => $this->original_open_time,
|
||||||
|
'close_time' => $this->original_close_time,
|
||||||
|
'close_reason' => null,
|
||||||
|
'reopen_date' => null,
|
||||||
|
'original_open_time' => null,
|
||||||
|
'original_close_time' => null,
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close venue with reason and reopen date
|
||||||
|
*/
|
||||||
|
public function closeVenue($reason, $reopenDate)
|
||||||
|
{
|
||||||
|
// Simpan jam operasional saat ini sebelum mengubahnya
|
||||||
|
$currentOpenTime = $this->open_time;
|
||||||
|
$currentCloseTime = $this->close_time;
|
||||||
|
|
||||||
|
$this->update([
|
||||||
|
'status' => 'close',
|
||||||
|
'close_reason' => $reason,
|
||||||
|
'reopen_date' => $reopenDate,
|
||||||
|
'original_open_time' => $currentOpenTime, // Simpan jam asli
|
||||||
|
'original_close_time' => $currentCloseTime, // Simpan jam asli
|
||||||
|
'open_time' => '00:00', // Set ke 00:00 setelah menyimpan original
|
||||||
|
'close_time' => '00:00', // Set ke 00:00 setelah menyimpan original
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open venue manually
|
||||||
|
*/
|
||||||
|
public function openVenue()
|
||||||
|
{
|
||||||
|
$this->update([
|
||||||
|
'status' => 'open',
|
||||||
|
'open_time' => $this->original_open_time ?: $this->open_time,
|
||||||
|
'close_time' => $this->original_close_time ?: $this->close_time,
|
||||||
|
'close_reason' => null,
|
||||||
|
'reopen_date' => null,
|
||||||
|
'original_open_time' => null,
|
||||||
|
'original_close_time' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('venues', function (Blueprint $table) {
|
||||||
|
$table->enum('status', ['open', 'close'])->default('open')->after('close_time');
|
||||||
|
$table->text('close_reason')->nullable()->after('status');
|
||||||
|
$table->date('reopen_date')->nullable()->after('close_reason');
|
||||||
|
$table->time('original_open_time')->nullable()->after('reopen_date');
|
||||||
|
$table->time('original_close_time')->nullable()->after('original_open_time');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('venues', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['status', 'close_reason', 'reopen_date', 'original_open_time', 'original_close_time']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -9,6 +9,25 @@
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Kelola Venue</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Kelola Venue</h1>
|
||||||
<p class="text-gray-600 mt-1">Kelola informasi venue Anda</p>
|
<p class="text-gray-600 mt-1">Kelola informasi venue Anda</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex space-x-3">
|
||||||
|
<!-- Venue Status Toggle -->
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<span class="text-sm font-medium text-gray-700">Status Venue:</span>
|
||||||
|
<div class="relative">
|
||||||
|
<button id="statusToggle"
|
||||||
|
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 {{ $venue->status === 'open' ? 'bg-green-600' : 'bg-red-600' }}"
|
||||||
|
onclick="toggleVenueStatus()">
|
||||||
|
<span class="sr-only">Toggle venue status</span>
|
||||||
|
<span
|
||||||
|
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {{ $venue->status === 'open' ? 'translate-x-6' : 'translate-x-1' }}"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span id="statusText"
|
||||||
|
class="text-sm font-medium {{ $venue->status === 'open' ? 'text-green-600' : 'text-red-600' }}">
|
||||||
|
{{ $venue->status === 'open' ? 'Buka' : 'Tutup' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ route('admin.venue.edit') }}"
|
<a href="{{ route('admin.venue.edit') }}"
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors">
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline mr-2" fill="none" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline mr-2" fill="none" viewBox="0 0 24 24"
|
||||||
|
@ -20,6 +39,7 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Alert Messages -->
|
<!-- Alert Messages -->
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
|
@ -34,6 +54,31 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<!-- Venue Status Alert -->
|
||||||
|
{{-- @if($venue->status === 'close')
|
||||||
|
<div class="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||||
|
fill="currentColor">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-red-800">Venue Sedang Tutup</h3>
|
||||||
|
<div class="mt-2 text-sm text-red-700">
|
||||||
|
<p><strong>Alasan:</strong> {{ $venue->close_reason }}</p>
|
||||||
|
@if($venue->reopen_date)
|
||||||
|
<p><strong>Akan buka kembali pada:</strong> {{ $venue->reopen_date->format('d M Y') }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif --}}
|
||||||
|
|
||||||
<!-- Venue Information Card -->
|
<!-- Venue Information Card -->
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200">
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
|
@ -92,17 +137,40 @@ class="w-full h-48 object-cover rounded-lg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<div class="flex items-start">
|
||||||
|
<div class="flex-shrink-0 w-32">
|
||||||
|
<span class="text-sm font-medium text-gray-500">Status:</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $venue->status === 'open' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
|
||||||
|
{{ $venue->status === 'open' ? 'Buka' : 'Tutup' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Operating Hours -->
|
<!-- Operating Hours -->
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<div class="flex-shrink-0 w-32">
|
<div class="flex-shrink-0 w-32">
|
||||||
<span class="text-sm font-medium text-gray-500">Jam Operasional:</span>
|
<span class="text-sm font-medium text-gray-500">Jam Operasional:</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
@if($venue->status === 'open')
|
||||||
<span class="text-sm text-gray-900">
|
<span class="text-sm text-gray-900">
|
||||||
{{ $venue->open_time ? \Carbon\Carbon::parse($venue->open_time)->format('H:i') : '-' }}
|
{{ $venue->open_time ? \Carbon\Carbon::parse($venue->open_time)->format('H:i') : '-' }}
|
||||||
-
|
-
|
||||||
{{ $venue->close_time ? \Carbon\Carbon::parse($venue->close_time)->format('H:i') : '-' }}
|
{{ $venue->close_time ? \Carbon\Carbon::parse($venue->close_time)->format('H:i') : '-' }}
|
||||||
</span>
|
</span>
|
||||||
|
@else
|
||||||
|
<span class="text-sm text-red-600">Tutup Sementara</span>
|
||||||
|
@if($venue->original_open_time && $venue->original_close_time)
|
||||||
|
<div class="text-xs text-gray-500 mt-1">
|
||||||
|
Jam normal: {{ \Carbon\Carbon::parse($venue->original_open_time)->format('H:i') }} -
|
||||||
|
{{ \Carbon\Carbon::parse($venue->original_close_time)->format('H:i') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -168,7 +236,8 @@ class="text-sm text-gray-900">{{ $venue->updated_at->format('d M Y, H:i') }}</sp
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<p class="text-sm font-medium text-gray-500">Meja Tersedia</p>
|
<p class="text-sm font-medium text-gray-500">Meja Tersedia</p>
|
||||||
<p class="text-2xl font-semibold text-gray-900">
|
<p class="text-2xl font-semibold text-gray-900">
|
||||||
{{ $venue->tables->where('status', 'available')->count() }}</p>
|
{{ $venue->tables->where('status', 'available')->count() }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,10 +257,255 @@ class="text-sm text-gray-900">{{ $venue->updated_at->format('d M Y, H:i') }}</sp
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<p class="text-sm font-medium text-gray-500">Meja Terpakai</p>
|
<p class="text-sm font-medium text-gray-500">Meja Terpakai</p>
|
||||||
<p class="text-2xl font-semibold text-gray-900">
|
<p class="text-2xl font-semibold text-gray-900">
|
||||||
{{ $venue->tables->where('status', 'occupied')->count() }}</p>
|
{{ $venue->tables->where('status', 'occupied')->count() }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Close Venue Modal -->
|
||||||
|
<div id="closeVenueModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden">
|
||||||
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
|
||||||
|
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-center">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">Tutup Venue</h3>
|
||||||
|
<div class="mt-4 text-left">
|
||||||
|
<form id="closeVenueForm">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="closeReason" class="block text-sm font-medium text-gray-700 mb-2">Alasan
|
||||||
|
Penutupan *</label>
|
||||||
|
<textarea id="closeReason" name="close_reason" rows="3"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="Masukkan alasan penutupan venue..." required></textarea>
|
||||||
|
<div id="closeReasonError" class="text-red-500 text-sm mt-1 hidden"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="reopenDate" class="block text-sm font-medium text-gray-700 mb-2">Tanggal Buka
|
||||||
|
Kembali *</label>
|
||||||
|
<input type="date" id="reopenDate" name="reopen_date"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
min="{{ date('Y-m-d', strtotime('+1 day')) }}" required>
|
||||||
|
<div id="reopenDateError" class="text-red-500 text-sm mt-1 hidden"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end space-x-3 mt-4">
|
||||||
|
<button type="button" onclick="closeModal()"
|
||||||
|
class="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 transition-colors">
|
||||||
|
Batal
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="confirmCloseVenue()"
|
||||||
|
class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors">
|
||||||
|
Tutup Venue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading Spinner -->
|
||||||
|
<div id="loadingSpinner" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center hidden">
|
||||||
|
<div class="bg-white p-4 rounded-lg">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
||||||
|
<span class="text-gray-700">Memproses...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let currentVenueStatus = '{{ $venue->status }}';
|
||||||
|
|
||||||
|
function toggleVenueStatus() {
|
||||||
|
if (currentVenueStatus === 'open') {
|
||||||
|
// Show close venue modal
|
||||||
|
document.getElementById('closeVenueModal').classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
// Open venue directly
|
||||||
|
confirmToggleStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById('closeVenueModal').classList.add('hidden');
|
||||||
|
// Clear form
|
||||||
|
document.getElementById('closeVenueForm').reset();
|
||||||
|
clearErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearErrors() {
|
||||||
|
document.getElementById('closeReasonError').classList.add('hidden');
|
||||||
|
document.getElementById('reopenDateError').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmCloseVenue() {
|
||||||
|
const closeReason = document.getElementById('closeReason').value.trim();
|
||||||
|
const reopenDate = document.getElementById('reopenDate').value;
|
||||||
|
|
||||||
|
// Clear previous errors
|
||||||
|
clearErrors();
|
||||||
|
|
||||||
|
// Validate form
|
||||||
|
let hasError = false;
|
||||||
|
|
||||||
|
if (!closeReason) {
|
||||||
|
document.getElementById('closeReasonError').textContent = 'Alasan penutupan harus diisi.';
|
||||||
|
document.getElementById('closeReasonError').classList.remove('hidden');
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reopenDate) {
|
||||||
|
document.getElementById('reopenDateError').textContent = 'Tanggal buka kembali harus diisi.';
|
||||||
|
document.getElementById('reopenDateError').classList.remove('hidden');
|
||||||
|
hasError = true;
|
||||||
|
} else {
|
||||||
|
const today = new Date();
|
||||||
|
const selectedDate = new Date(reopenDate);
|
||||||
|
if (selectedDate <= today) {
|
||||||
|
document.getElementById('reopenDateError').textContent = 'Tanggal buka kembali harus setelah hari ini.';
|
||||||
|
document.getElementById('reopenDateError').classList.remove('hidden');
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal and proceed with toggle
|
||||||
|
closeModal();
|
||||||
|
confirmToggleStatus({
|
||||||
|
close_reason: closeReason,
|
||||||
|
reopen_date: reopenDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmToggleStatus(data = {}) {
|
||||||
|
// Show loading spinner
|
||||||
|
document.getElementById('loadingSpinner').classList.remove('hidden');
|
||||||
|
|
||||||
|
// Prepare request data
|
||||||
|
const requestData = {
|
||||||
|
_token: '{{ csrf_token() }}',
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch('{{ route("admin.venue.toggle-status") }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestData)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Hide loading spinner
|
||||||
|
document.getElementById('loadingSpinner').classList.add('hidden');
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// Update UI
|
||||||
|
updateVenueStatusUI(data.venue);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showAlert(data.message, 'success');
|
||||||
|
|
||||||
|
// Update current status
|
||||||
|
currentVenueStatus = data.status;
|
||||||
|
|
||||||
|
// Reload page after 2 seconds to refresh all data
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
if (data.errors) {
|
||||||
|
// Handle validation errors
|
||||||
|
let errorMessage = 'Terjadi kesalahan validasi:\n';
|
||||||
|
Object.values(data.errors).forEach(error => {
|
||||||
|
errorMessage += '- ' + error[0] + '\n';
|
||||||
|
});
|
||||||
|
showAlert(errorMessage, 'error');
|
||||||
|
} else {
|
||||||
|
showAlert(data.error || 'Terjadi kesalahan yang tidak diketahui', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Hide loading spinner
|
||||||
|
document.getElementById('loadingSpinner').classList.add('hidden');
|
||||||
|
|
||||||
|
console.error('Error:', error);
|
||||||
|
showAlert('Venue ditutup sementara!', 'error');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVenueStatusUI(venue) {
|
||||||
|
const toggle = document.getElementById('statusToggle');
|
||||||
|
const statusText = document.getElementById('statusText');
|
||||||
|
const toggleButton = toggle.querySelector('span:last-child');
|
||||||
|
|
||||||
|
if (venue.status === 'open') {
|
||||||
|
toggle.classList.remove('bg-red-600');
|
||||||
|
toggle.classList.add('bg-green-600');
|
||||||
|
toggleButton.classList.remove('translate-x-1');
|
||||||
|
toggleButton.classList.add('translate-x-6');
|
||||||
|
statusText.textContent = 'Buka';
|
||||||
|
statusText.classList.remove('text-red-600');
|
||||||
|
statusText.classList.add('text-green-600');
|
||||||
|
} else {
|
||||||
|
toggle.classList.remove('bg-green-600');
|
||||||
|
toggle.classList.add('bg-red-600');
|
||||||
|
toggleButton.classList.remove('translate-x-6');
|
||||||
|
toggleButton.classList.add('translate-x-1');
|
||||||
|
statusText.textContent = 'Tutup';
|
||||||
|
statusText.classList.remove('text-green-600');
|
||||||
|
statusText.classList.add('text-red-600');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlert(message, type) {
|
||||||
|
// Remove existing alerts
|
||||||
|
const existingAlerts = document.querySelectorAll('.alert-message');
|
||||||
|
existingAlerts.forEach(alert => alert.remove());
|
||||||
|
|
||||||
|
// Create new alert
|
||||||
|
const alertDiv = document.createElement('div');
|
||||||
|
alertDiv.className = `alert-message mb-6 px-4 py-3 rounded-lg ${type === 'success' ? 'bg-green-100 border border-green-400 text-green-700' : 'bg-red-100 border border-red-400 text-red-700'
|
||||||
|
}`;
|
||||||
|
alertDiv.textContent = message;
|
||||||
|
|
||||||
|
// Insert alert after header
|
||||||
|
const header = document.querySelector('.mb-6');
|
||||||
|
header.parentNode.insertBefore(alertDiv, header.nextSibling);
|
||||||
|
|
||||||
|
// Auto remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
alertDiv.remove();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set minimum date for reopen date input
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const tomorrow = new Date();
|
||||||
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
|
const minDate = tomorrow.toISOString().split('T')[0];
|
||||||
|
document.getElementById('reopenDate').setAttribute('min', minDate);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
|
@ -58,11 +58,32 @@ class="flex flex-col h-full border border-gray-400 rounded-lg overflow-hidden">
|
||||||
<div class="flex-grow px-4 py-2">
|
<div class="flex-grow px-4 py-2">
|
||||||
<h3 class="text-sm text-gray-400 font-semibold mb-2">Venue</h3>
|
<h3 class="text-sm text-gray-400 font-semibold mb-2">Venue</h3>
|
||||||
<h1 class="text-xl text-gray-800 font-semibold">{{ $venue->name }}</h1>
|
<h1 class="text-xl text-gray-800 font-semibold">{{ $venue->name }}</h1>
|
||||||
|
@if($venue['status'] === 'open')
|
||||||
|
{{-- Venue sedang buka - tampilkan jam operasional --}}
|
||||||
<p class="text-sm text-gray-600 mt-1">
|
<p class="text-sm text-gray-600 mt-1">
|
||||||
<i class="fa-regular fa-clock"></i>
|
<i class="fa-regular fa-clock text-green-500"></i>
|
||||||
Buka: {{ date('H:i', strtotime($venue['open_time'])) }} -
|
Buka: {{ date('H:i', strtotime($venue['open_time'])) }} -
|
||||||
{{ date('H:i', strtotime($venue['close_time'])) }}
|
{{ date('H:i', strtotime($venue['close_time'])) }}
|
||||||
</p>
|
</p>
|
||||||
|
@else
|
||||||
|
{{-- Venue sedang tutup - tampilkan informasi penutupan --}}
|
||||||
|
<div class="mt-1">
|
||||||
|
<p class="text-sm text-red-600 font-medium">
|
||||||
|
<i class="fa-solid fa-circle-xmark text-red-500"></i>
|
||||||
|
Tutup Sementara - {{ $venue['close_reason'] }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
@if(!empty($venue['reopen_date']))
|
||||||
|
<p class="text-xs text-gray-500 mt-1">
|
||||||
|
<i class="fa-regular fa-calendar"></i>
|
||||||
|
<strong>Buka kembali:</strong>
|
||||||
|
{{ \Carbon\Carbon::parse($venue['reopen_date'])->format('d M Y') }} - Jam
|
||||||
|
{{ date('H:i', strtotime($venue['original_open_time'])) }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<p class="mt-10 text-gray-500 text-sm">Mulai:
|
<p class="mt-10 text-gray-500 text-sm">Mulai:
|
||||||
<span class="font-bold text-gray-800">Rp30,000</span>
|
<span class="font-bold text-gray-800">Rp30,000</span>
|
||||||
<span class="text-gray-400 font-thin text-sm">/ jam</span>
|
<span class="text-gray-400 font-thin text-sm">/ jam</span>
|
||||||
|
|
|
@ -22,11 +22,22 @@ class="w-full h-full object-cover rounded-lg mb-4 mt-8" />
|
||||||
|
|
||||||
<h1 class="text-xl text-gray-800 font-semibold">{{ $venue['name'] }}</h1>
|
<h1 class="text-xl text-gray-800 font-semibold">{{ $venue['name'] }}</h1>
|
||||||
<p class="text-sm text-gray-500">{{ $venue['location'] ?? 'Lokasi tidak tersedia' }}</p>
|
<p class="text-sm text-gray-500">{{ $venue['location'] ?? 'Lokasi tidak tersedia' }}</p>
|
||||||
|
@if($venue['status'] === 'open')
|
||||||
|
{{-- Venue sedang buka - tampilkan jam operasional --}}
|
||||||
<p class="text-sm text-gray-600 mt-1">
|
<p class="text-sm text-gray-600 mt-1">
|
||||||
<i class="fa-regular fa-clock"></i>
|
<i class="fa-regular fa-clock text-green-500"></i>
|
||||||
Jam Operasional: {{ date('H:i', strtotime($venue['open_time'])) }} -
|
Jam Operasional: {{ date('H:i', strtotime($venue['open_time'])) }} -
|
||||||
{{ date('H:i', strtotime($venue['close_time'])) }}
|
{{ date('H:i', strtotime($venue['close_time'])) }}
|
||||||
</p>
|
</p>
|
||||||
|
@else
|
||||||
|
{{-- Venue sedang tutup - tampilkan informasi penutupan --}}
|
||||||
|
<div class="mt-1">
|
||||||
|
<p class="text-sm text-red-600 font-medium">
|
||||||
|
<i class="fa-solid fa-circle-xmark text-red-500"></i>
|
||||||
|
Tutup Sementara - {{ $venue['close_reason'] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<a href="https://www.google.com/maps/search/?api=1&query={{ urlencode($venue['address']) }}" target="_blank"
|
<a href="https://www.google.com/maps/search/?api=1&query={{ urlencode($venue['address']) }}" target="_blank"
|
||||||
class="flex items-center bg-[url('/public/images/map.jpg')] bg-cover bg-center p-4">
|
class="flex items-center bg-[url('/public/images/map.jpg')] bg-cover bg-center p-4">
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
Route::get('/venue', [AdminVenueController::class, 'index'])->name('admin.venue.index');
|
Route::get('/venue', [AdminVenueController::class, 'index'])->name('admin.venue.index');
|
||||||
Route::get('/venue/edit', [AdminVenueController::class, 'edit'])->name('admin.venue.edit');
|
Route::get('/venue/edit', [AdminVenueController::class, 'edit'])->name('admin.venue.edit');
|
||||||
Route::put('/venue/update', [AdminVenueController::class, 'update'])->name('admin.venue.update');
|
Route::put('/venue/update', [AdminVenueController::class, 'update'])->name('admin.venue.update');
|
||||||
|
Route::post('/venue/toggle-status', [AdminVenueController::class, 'toggleStatus'])->name('admin.venue.toggle-status');
|
||||||
|
|
||||||
// Revenue management routes
|
// Revenue management routes
|
||||||
Route::get('/revenues', [RevenueController::class, 'index'])->name('admin.revenues.index');
|
Route::get('/revenues', [RevenueController::class, 'index'])->name('admin.revenues.index');
|
||||||
|
|
Loading…
Reference in New Issue