Revenues Admin

This commit is contained in:
Stephen Gesityan 2025-05-14 19:08:34 +07:00
parent 1ccfbfa336
commit 1429115bf8
14 changed files with 1468 additions and 302 deletions

View File

@ -8,12 +8,35 @@
use App\Models\Table;
use App\Models\Booking;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
class AdminController extends Controller
{
public function index()
{
$venue = Venue::find(auth()->user()->venue_id);
// Get current admin's venue ID
$user = Auth::user();
$adminVenueId = $user->venue_id;
$isSuperAdmin = $user->hasRole('superadmin') || $adminVenueId === null;
// For super admin, get the first venue or allow selection
if ($isSuperAdmin) {
$venue = request()->has('venue_id')
? Venue::find(request('venue_id'))
: Venue::first();
// Get all venues for dropdown selection
$venues = Venue::all();
} else {
$venue = Venue::find($adminVenueId);
$venues = collect([$venue]);
}
// Jika tidak ada venue, tampilkan halaman khusus
if (!$venue) {
return view('admin.no_venue_dashboard');
}
// Menghitung booking hari ini
$todayBookings = Booking::whereDate('created_at', now())
@ -32,7 +55,7 @@ public function index()
->whereHas('table', function ($query) use ($venue) {
$query->where('venue_id', $venue->id);
})
->where('status', 'paid')
->where('bookings.status', 'paid')
->sum('total_amount');
// Menghitung pendapatan bulan ini
@ -41,20 +64,20 @@ public function index()
->whereHas('table', function ($query) use ($venue) {
$query->where('venue_id', $venue->id);
})
->where('status', 'paid')
->where('bookings.status', 'paid')
->sum('total_amount');
// Menghitung jumlah booking berdasarkan status
$pendingBookings = Booking::whereHas('table', function ($query) use ($venue) {
$query->where('venue_id', $venue->id);
})
->where('status', 'pending')
->where('bookings.status', 'pending')
->count();
$paidBookings = Booking::whereHas('table', function ($query) use ($venue) {
$query->where('venue_id', $venue->id);
})
->where('status', 'paid')
->where('bookings.status', 'paid')
->count();
// Ambil booking terbaru
@ -66,7 +89,7 @@ public function index()
->with(['user', 'table'])
->get();
// Menghitung data analitik untuk diagram
// Menghitung data analitik untuk diagram booking 7 hari terakhir
$lastWeekBookings = [];
for ($i = 6; $i >= 0; $i--) {
$date = now()->subDays($i);
@ -81,8 +104,118 @@ public function index()
];
}
// NEW: MONTHLY REVENUE FOR LAST 6 MONTHS
$lastSixMonthsRevenue = [];
for ($i = 5; $i >= 0; $i--) {
$month = now()->subMonths($i);
$revenue = Booking::whereMonth('created_at', $month->month)
->whereYear('created_at', $month->year)
->whereHas('table', function ($query) use ($venue) {
$query->where('venue_id', $venue->id);
})
->where('bookings.status', 'paid')
->sum('total_amount');
$lastSixMonthsRevenue[] = [
'month' => $month->format('M Y'),
'revenue' => $revenue
];
}
// FIXED: REVENUE PER TABLE - AMBIL DATA LEBIH FLEKSIBEL
// Ubah untuk mengambil data dari 6 bulan terakhir jika bulan ini tidak ada data
$tableRevenue = Booking::where(function($query) {
// Coba ambil dari bulan ini dulu
$query->whereMonth('bookings.created_at', now()->month)
->whereYear('bookings.created_at', now()->year);
})
->whereHas('table', function ($query) use ($venue) {
$query->where('venue_id', $venue->id);
})
->where('bookings.status', 'paid')
->select(
'table_id',
DB::raw('tables.name as table_name'),
DB::raw('COUNT(*) as booking_count'),
DB::raw('SUM(bookings.total_amount) as table_revenue')
)
->join('tables', 'bookings.table_id', '=', 'tables.id')
->groupBy('table_id', 'tables.name')
->orderBy('table_revenue', 'desc')
->take(10)
->get();
// Jika tidak ada data bulan ini, coba ambil dari 6 bulan terakhir
if ($tableRevenue->isEmpty()) {
$tableRevenue = Booking::where(function($query) {
// Ambil dari 6 bulan terakhir
$query->where('bookings.created_at', '>=', now()->subMonths(6));
})
->whereHas('table', function ($query) use ($venue) {
$query->where('venue_id', $venue->id);
})
->where('bookings.status', 'paid')
->select(
'table_id',
DB::raw('tables.name as table_name'),
DB::raw('COUNT(*) as booking_count'),
DB::raw('SUM(bookings.total_amount) as table_revenue')
)
->join('tables', 'bookings.table_id', '=', 'tables.id')
->groupBy('table_id', 'tables.name')
->orderBy('table_revenue', 'desc')
->take(10)
->get();
}
// Jika masih tidak ada data, buat dummy data supaya chart tetap muncul
if ($tableRevenue->isEmpty()) {
// Ambil 5 meja dari venue ini
$tables = Table::where('venue_id', $venue->id)
->take(5)
->get();
foreach ($tables as $table) {
$tableRevenue->push([
'table_id' => $table->id,
'table_name' => $table->name,
'booking_count' => 0,
'table_revenue' => 0
]);
}
// Jika tidak ada meja sama sekali, buat data dummy
if ($tableRevenue->isEmpty()) {
$tableRevenue = collect([
[
'table_id' => 1,
'table_name' => 'Meja 1',
'booking_count' => 0,
'table_revenue' => 0
],
[
'table_id' => 2,
'table_name' => 'Meja 2',
'booking_count' => 0,
'table_revenue' => 0
],
[
'table_id' => 3,
'table_name' => 'Meja 3',
'booking_count' => 0,
'table_revenue' => 0
]
]);
}
}
// NEW: REMOVE REVENUE BY DAY OF WEEK
// Hapus bagian revenueByDay karena tidak diperlukan lagi
return view('admin.dashboard', compact(
'venue',
'venues',
'isSuperAdmin',
'todayBookings',
'totalTables',
'usedTables',
@ -92,7 +225,10 @@ public function index()
'monthlyRevenue',
'pendingBookings',
'paidBookings',
'lastWeekBookings'
'lastWeekBookings',
'lastSixMonthsRevenue',
'tableRevenue'
// Hapus 'revenueByDay' dari compact
));
}
}

View File

@ -0,0 +1,261 @@
<?php
namespace App\Http\Controllers\admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Booking;
use App\Models\Table;
use App\Models\Venue;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
class RevenueController extends Controller
{
public function index(Request $request)
{
// Default filter periode (bulan ini)
$startDate = $request->input('start_date', Carbon::now()->startOfMonth()->format('Y-m-d'));
$endDate = $request->input('end_date', Carbon::now()->format('Y-m-d'));
// Get current admin's venue ID
$user = Auth::user();
$adminVenueId = $user->venue_id; // Asumsi: Admin memiliki venue_id yang menunjukkan venue yang mereka kelola
// Jika admin adalah super admin (bisa melihat semua venue)
$isSuperAdmin = $user->hasRole('superadmin') || $adminVenueId === null; // Asumsi: Super admin tidak memiliki venue_id spesifik atau memiliki role khusus
// Query untuk mengambil data venue untuk filter (hanya venue yang dikelola oleh admin atau semua venue untuk super admin)
if ($isSuperAdmin) {
$venues = Venue::all();
$venueId = $request->input('venue_id');
} else {
$venues = Venue::where('id', $adminVenueId)->get();
$venueId = $adminVenueId; // Force venue id ke venue yang dikelola admin
}
// Base query untuk pendapatan
$revenueQuery = Booking::with('table.venue')
->where('bookings.status', 'paid')
->whereBetween(DB::raw('DATE(bookings.start_time)'), [$startDate, $endDate]);
// Filter berdasarkan venue yang dikelola admin atau yang dipilih oleh super admin
if (!$isSuperAdmin) {
// Admin venue biasa hanya bisa melihat venuenya sendiri
$revenueQuery->whereHas('table', function($query) use ($adminVenueId) {
$query->where('venue_id', $adminVenueId);
});
} elseif ($venueId) {
// Super admin bisa memilih venue tertentu
$revenueQuery->whereHas('table', function($query) use ($venueId) {
$query->where('venue_id', $venueId);
});
}
// Get summary total pendapatan
$totalRevenue = $revenueQuery->sum('total_amount');
// Get total bookings
$totalBookings = $revenueQuery->count();
// Get revenue per venue - dengan filter sesuai akses admin
$revenuePerVenueQuery = Booking::with('table.venue')
->where('bookings.status', 'paid')
->whereBetween(DB::raw('DATE(bookings.start_time)'), [$startDate, $endDate]);
if (!$isSuperAdmin) {
$revenuePerVenueQuery->whereHas('table', function($query) use ($adminVenueId) {
$query->where('venue_id', $adminVenueId);
});
}
$revenuePerVenue = $revenuePerVenueQuery
->select(
'tables.venue_id',
DB::raw('venues.name as venue_name'),
DB::raw('COUNT(*) as total_bookings'),
DB::raw('SUM(bookings.total_amount) as total_revenue')
)
->join('tables', 'bookings.table_id', '=', 'tables.id')
->join('venues', 'tables.venue_id', '=', 'venues.id')
->groupBy('tables.venue_id', 'venues.name')
->get();
// Get revenue per table (Untuk admin biasa, selalu tampilkan detail meja venuenya)
// Untuk super admin, detail meja hanya muncul jika venue tertentu dipilih
$revenuePerTable = null;
if (!$isSuperAdmin || $venueId) {
$venueIdForTable = $isSuperAdmin ? $venueId : $adminVenueId;
$revenuePerTable = Booking::with('table')
->where('bookings.status', 'paid')
->whereBetween(DB::raw('DATE(bookings.start_time)'), [$startDate, $endDate])
->whereHas('table', function($query) use ($venueIdForTable) {
$query->where('venue_id', $venueIdForTable);
})
->select(
'table_id',
DB::raw('tables.name as table_name'),
DB::raw('COUNT(*) as booking_count'),
DB::raw('SUM(bookings.total_amount) as table_revenue')
)
->join('tables', 'bookings.table_id', '=', 'tables.id')
->groupBy('table_id', 'tables.name')
->get();
}
// Get data untuk chart pendapatan harian dalam periode
$dailyRevenueQuery = Booking::with('table.venue')
->where('bookings.status', 'paid')
->whereBetween(DB::raw('DATE(bookings.start_time)'), [$startDate, $endDate]);
if (!$isSuperAdmin) {
$dailyRevenueQuery->whereHas('table', function($query) use ($adminVenueId) {
$query->where('venue_id', $adminVenueId);
});
} elseif ($venueId) {
$dailyRevenueQuery->whereHas('table', function($query) use ($venueId) {
$query->where('venue_id', $venueId);
});
}
$dailyRevenue = $dailyRevenueQuery
->select(
DB::raw('DATE(bookings.start_time) as date'),
DB::raw('SUM(bookings.total_amount) as revenue')
)
->groupBy(DB::raw('DATE(bookings.start_time)'))
->orderBy('date', 'asc')
->get();
return view('admin.revenues.index', compact(
'venues',
'venueId',
'totalRevenue',
'totalBookings',
'revenuePerVenue',
'revenuePerTable',
'dailyRevenue',
'startDate',
'endDate',
'isSuperAdmin'
));
}
public function detail(Request $request, $tableId)
{
// Default filter periode (bulan ini)
$startDate = $request->input('start_date', Carbon::now()->startOfMonth()->format('Y-m-d'));
$endDate = $request->input('end_date', Carbon::now()->format('Y-m-d'));
// Get table detail
$table = Table::with('venue')->findOrFail($tableId);
// Cek apakah admin memiliki akses ke meja ini
$user = Auth::user();
$adminVenueId = $user->venue_id;
$isSuperAdmin = $user->hasRole('superadmin') || $adminVenueId === null;
// Jika bukan super admin dan meja bukan dari venue yang dikelola, tolak akses
if (!$isSuperAdmin && $table->venue_id != $adminVenueId) {
abort(403, 'Tidak memiliki akses ke meja ini');
}
// Query untuk detail booking meja tersebut
$bookings = Booking::where('table_id', $tableId)
->where('bookings.status', 'paid')
->whereBetween(DB::raw('DATE(bookings.start_time)'), [$startDate, $endDate])
->with('user')
->orderBy('start_time', 'desc')
->get();
// Hitung total pendapatan untuk meja ini di periode
$totalRevenue = $bookings->sum('total_amount');
// Hitung total jam penggunaan
$totalHours = $bookings->sum(function($booking) {
$start = Carbon::parse($booking->start_time);
$end = Carbon::parse($booking->end_time);
return $end->diffInHours($start);
});
return view('admin.revenues.detail', compact(
'table',
'bookings',
'totalRevenue',
'totalHours',
'startDate',
'endDate'
));
}
public function export(Request $request)
{
// Get current admin's venue ID
$user = Auth::user();
$adminVenueId = $user->venue_id;
$isSuperAdmin = $user->hasRole('superadmin') || $adminVenueId === null;
$startDate = $request->input('start_date', Carbon::now()->startOfMonth()->format('Y-m-d'));
$endDate = $request->input('end_date', Carbon::now()->format('Y-m-d'));
$venueId = $isSuperAdmin ? $request->input('venue_id') : $adminVenueId;
// Base query untuk pendapatan
$bookingsQuery = Booking::with(['table.venue', 'user'])
->where('bookings.status', 'paid')
->whereBetween(DB::raw('DATE(bookings.start_time)'), [$startDate, $endDate]);
// Filter berdasarkan venue sesuai hak akses admin
if (!$isSuperAdmin) {
$bookingsQuery->whereHas('table', function($query) use ($adminVenueId) {
$query->where('venue_id', $adminVenueId);
});
} elseif ($venueId) {
$bookingsQuery->whereHas('table', function($query) use ($venueId) {
$query->where('venue_id', $venueId);
});
}
$bookings = $bookingsQuery->get();
// Export logic using Laravel Excel or simple CSV download
// For now we'll return a simple array that could be converted to CSV/Excel
$exportData = [];
foreach ($bookings as $booking) {
$exportData[] = [
'id' => $booking->id,
'user' => $booking->user->name,
'venue' => $booking->table->venue->name,
'table' => $booking->table->name,
'start_time' => $booking->start_time->format('Y-m-d H:i'),
'end_time' => $booking->end_time->format('Y-m-d H:i'),
'duration_hours' => $booking->end_time->diffInHours($booking->start_time),
'payment_method' => $booking->payment_method,
'total_amount' => $booking->total_amount,
];
}
// Return CSV response (simplified example)
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="venue-revenue-report.csv"',
];
// Convert array to CSV string
$callback = function() use ($exportData) {
$file = fopen('php://output', 'w');
// Header row
fputcsv($file, array_keys($exportData[0] ?? []));
// Data rows
foreach ($exportData as $row) {
fputcsv($file, $row);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}

View File

@ -55,6 +55,17 @@ public function isAdmin()
return $this->role === 'admin';
}
/**
* Check if the user has a specific role.
*
* @param string $role
* @return bool
*/
public function hasRole($role)
{
return $this->role === $role;
}
/**
* Get the venue that the admin belongs to.
*/

View File

@ -70,7 +70,7 @@
|
*/
'timezone' => 'UTC',
'timezone' => 'Asia/Jakarta',
/*
|--------------------------------------------------------------------------

View File

@ -35,11 +35,13 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
<div class="w-full md:w-1/4">
<label for="status" class="block text-sm font-medium text-gray-700 mb-1">Status</label>
<select name="status" id="status" class="form-select w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-200">
<select name="status" id="status"
class="form-select w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-200">
<option value="">Semua Status</option>
<option value="booked" {{ request('status') == 'booked' ? 'selected' : '' }}>Booked</option>
<option value="selesai" {{ request('status') == 'selesai' ? 'selected' : '' }}>Selesai</option>
<option value="cancelled" {{ request('status') == 'cancelled' ? 'selected' : '' }}>Dibatalkan</option>
<option value="cancelled" {{ request('status') == 'cancelled' ? 'selected' : '' }}>Dibatalkan
</option>
</select>
</div>
@ -82,8 +84,10 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'user', 'direction' => request('direction') == 'asc' && request('sort') == 'user' ? 'desc' : 'asc'])) }}" class="flex items-center">
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'user', 'direction' => request('direction') == 'asc' && request('sort') == 'user' ? 'desc' : 'asc'])) }}"
class="flex items-center">
User
@if(request('sort') == 'user')
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
@ -92,8 +96,10 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
@endif
</a>
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'table', 'direction' => request('direction') == 'asc' && request('sort') == 'table' ? 'desc' : 'asc'])) }}" class="flex items-center">
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'table', 'direction' => request('direction') == 'asc' && request('sort') == 'table' ? 'desc' : 'asc'])) }}"
class="flex items-center">
Meja
@if(request('sort') == 'table')
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
@ -102,8 +108,10 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
@endif
</a>
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'start_time', 'direction' => request('direction') == 'asc' && request('sort') == 'start_time' ? 'desc' : 'asc'])) }}" class="flex items-center">
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'start_time', 'direction' => request('direction') == 'asc' && request('sort') == 'start_time' ? 'desc' : 'asc'])) }}"
class="flex items-center">
Mulai
@if(request('sort') == 'start_time')
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
@ -112,8 +120,10 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
@endif
</a>
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'end_time', 'direction' => request('direction') == 'asc' && request('sort') == 'end_time' ? 'desc' : 'asc'])) }}" class="flex items-center">
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'end_time', 'direction' => request('direction') == 'asc' && request('sort') == 'end_time' ? 'desc' : 'asc'])) }}"
class="flex items-center">
Selesai
@if(request('sort') == 'end_time')
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
@ -122,19 +132,22 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
@endif
</a>
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'status', 'direction' => request('direction') == 'asc' && request('sort') == 'status' ? 'desc' : 'asc'])) }}" class="flex items-center">
{{-- <th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'status', 'direction' => request('direction') == 'asc' && request('sort') == 'status' ? 'desc' : 'asc'])) }}"
class="flex items-center">
Status
@if(request('sort') == 'status')
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
@else
<i class="fas fa-sort ml-1 opacity-50"></i>
<i class="fas fa-sort ml-1 opacity-50"></i>
@endif
</a>
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th> --}}
{{-- <th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Aksi
</th>
</th> --}}
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@ -143,7 +156,9 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<img class="h-10 w-10 rounded-full" src="https://ui-avatars.com/api/?name={{ urlencode($booking->user->name) }}&color=7F9CF5&background=EBF4FF" alt="">
<img class="h-10 w-10 rounded-full"
src="https://ui-avatars.com/api/?name={{ urlencode($booking->user->name) }}&color=7F9CF5&background=EBF4FF"
alt="">
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">{{ $booking->user->name }}</div>
@ -153,60 +168,72 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ $booking->table->name }}</div>
<div class="text-sm text-gray-500">Kapasitas: {{ $booking->table->capacity }} orang</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i') }}</div>
<div class="text-sm text-gray-500">{{ \Carbon\Carbon::parse($booking->start_time)->format('d M Y') }}</div>
<div class="text-sm text-gray-900">
{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i') }}</div>
<div class="text-sm text-gray-500">
{{ \Carbon\Carbon::parse($booking->start_time)->format('d M Y') }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">{{ \Carbon\Carbon::parse($booking->end_time)->format('H:i') }}</div>
<div class="text-sm text-gray-500">{{ \Carbon\Carbon::parse($booking->end_time)->format('d M Y') }}</div>
<div class="text-sm text-gray-900">
{{ \Carbon\Carbon::parse($booking->end_time)->format('H:i') }}</div>
<div class="text-sm text-gray-500">
{{ \Carbon\Carbon::parse($booking->end_time)->format('d M Y') }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{{-- <td class="px-6 py-4 whitespace-nowrap">
@if($booking->status === 'booked')
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
<i class="fas fa-clock mr-1"></i> Booked
</span>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
<i class="fas fa-clock mr-1"></i> Booked
</span>
@elseif($booking->status === 'selesai')
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
<i class="fas fa-check mr-1"></i> Selesai
</span>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
<i class="fas fa-check mr-1"></i> Selesai
</span>
@else
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
<i class="fas fa-times mr-1"></i> Dibatalkan
</span>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
<i class="fas fa-times mr-1"></i> Dibatalkan
</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
</td> --}}
{{-- <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div class="flex space-x-2">
<a href="{{ route('admin.bookings.show', $booking->id) }}" class="text-blue-600 hover:text-blue-900" title="Detail">
<a href="{{ route('admin.bookings.show', $booking->id) }}"
class="text-blue-600 hover:text-blue-900" title="Detail">
<i class="fas fa-eye"></i>
</a>
@if($booking->status === 'booked')
<a href="{{ route('admin.bookings.edit', $booking->id) }}" class="text-yellow-600 hover:text-yellow-900" title="Edit">
<a href="{{ route('admin.bookings.edit', $booking->id) }}"
class="text-yellow-600 hover:text-yellow-900" title="Edit">
<i class="fas fa-edit"></i>
</a>
<form action="{{ route('admin.bookings.complete', $booking->id) }}" method="POST" class="inline">
<form action="{{ route('admin.bookings.complete', $booking->id) }}" method="POST"
class="inline">
@csrf
@method('PATCH')
<button type="submit" class="text-green-600 hover:text-green-900" title="Selesai" onclick="return confirm('Apakah anda yakin menyelesaikan booking ini?')">
<button type="submit" class="text-green-600 hover:text-green-900" title="Selesai"
onclick="return confirm('Apakah anda yakin menyelesaikan booking ini?')">
<i class="fas fa-check-circle"></i>
</button>
</form>
<form action="{{ route('admin.bookings.cancel', $booking->id) }}" method="POST" class="inline">
<form action="{{ route('admin.bookings.cancel', $booking->id) }}" method="POST"
class="inline">
@csrf
@method('PATCH')
<button type="submit" class="text-red-600 hover:text-red-900" title="Batalkan" onclick="return confirm('Apakah anda yakin membatalkan booking ini?')">
<button type="submit" class="text-red-600 hover:text-red-900" title="Batalkan"
onclick="return confirm('Apakah anda yakin membatalkan booking ini?')">
<i class="fas fa-ban"></i>
</button>
</form>
@endif
</div>
</td>
</td> --}}
</tr>
@empty
<tr>
@ -226,7 +253,8 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
<div class="px-4 py-3 bg-gray-50 border-t border-gray-200">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-500">
Menampilkan {{ $bookings->firstItem() ?? 0 }} - {{ $bookings->lastItem() ?? 0 }} dari {{ $bookings->total() }} data
Menampilkan {{ $bookings->firstItem() ?? 0 }} - {{ $bookings->lastItem() ?? 0 }} dari
{{ $bookings->total() }} data
</div>
<div>
{{ $bookings->appends(request()->query())->links() }}
@ -278,17 +306,17 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
// Date range validation
const dateFrom = document.getElementById('date_from');
const dateTo = document.getElementById('date_to');
if (dateFrom && dateTo) {
dateFrom.addEventListener('change', function() {
dateFrom.addEventListener('change', function () {
dateTo.min = dateFrom.value;
});
dateTo.addEventListener('change', function() {
dateTo.addEventListener('change', function () {
dateFrom.max = dateTo.value;
});
}

View File

@ -89,7 +89,8 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
<div>
<p class="text-sm font-medium text-gray-500">Penggunaan Meja</p>
<p class="text-2xl font-bold text-gray-800">
{{ $totalTables > 0 ? round(($usedTables / $totalTables) * 100) : 0 }}%</p>
{{ $totalTables > 0 ? round(($usedTables / $totalTables) * 100) : 0 }}%
</p>
</div>
<div class="text-amber-500 p-2 bg-amber-50 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
@ -106,8 +107,8 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
</div>
</div>
<!-- Charts and Recent Bookings -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Charts Row 1 -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<!-- Weekly Bookings Chart -->
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm p-6">
<h2 class="font-semibold text-lg mb-4">Booking Per Hari (7 Hari Terakhir)</h2>
@ -139,11 +140,12 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
<p class="font-medium text-gray-800">{{ $booking->user->name }}</p>
<div class="flex items-center text-sm text-gray-500">
<span class="mr-2">{{ $booking->table->name }}</span>
<span class="text-xs px-2 py-0.5 rounded-full {{
$booking->status === 'paid' ? 'bg-green-100 text-green-800' :
<span
class="text-xs px-2 py-0.5 rounded-full {{
$booking->status === 'paid' ? 'bg-green-100 text-green-800' :
($booking->status === 'pending' ? 'bg-amber-100 text-amber-800' :
'bg-gray-100 text-gray-800')
}}">
}}">
{{ ucfirst($booking->status) }}
</span>
</div>
@ -158,6 +160,28 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
@endif
</div>
</div>
<!-- Charts Row 2 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<!-- Monthly Revenue Chart -->
<div class="bg-white rounded-xl shadow-sm p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="font-semibold text-lg">Pendapatan 6 Bulan Terakhir</h2>
</div>
<div class="h-80" id="monthlyRevenueChart"></div>
</div>
</div>
<!-- Charts Row 3 -->
<div class="grid grid-cols-1 gap-6">
<!-- Table Revenue Performance -->
<div class="bg-white rounded-xl shadow-sm p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="font-semibold text-lg">Performa Pendapatan per Meja (Bulan Ini)</h2>
</div>
<div class="h-96" id="tableRevenueChart"></div>
</div>
</div>
</div>
</div>
@ -224,6 +248,209 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
var chart = new ApexCharts(document.querySelector("#bookingsChart"), options);
chart.render();
// Monthly Revenue Chart
var monthlyRevenueData = @json($lastSixMonthsRevenue);
var monthlyRevenueOptions = {
series: [{
name: 'Pendapatan',
data: monthlyRevenueData.map(item => item.revenue)
}],
chart: {
type: 'area',
height: 300,
zoom: {
enabled: false
},
toolbar: {
show: false
}
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth',
width: 3
},
colors: ['#10b981'],
xaxis: {
categories: monthlyRevenueData.map(item => item.month),
axisBorder: {
show: false
}
},
yaxis: {
title: {
text: 'Pendapatan (Rp)'
},
labels: {
formatter: function (val) {
return 'Rp' + val.toLocaleString('id-ID');
}
}
},
tooltip: {
y: {
formatter: function (val) {
return 'Rp' + val.toLocaleString('id-ID');
}
}
},
fill: {
type: 'gradient',
gradient: {
shade: 'light',
type: "vertical",
shadeIntensity: 0.4,
inverseColors: false,
opacityFrom: 0.8,
opacityTo: 0.2,
stops: [0, 100]
}
},
grid: {
borderColor: '#f3f4f6',
strokeDashArray: 5
},
markers: {
size: 5,
colors: ['#10b981'],
strokeColor: '#fff',
strokeWidth: 2
}
};
var monthlyRevenueChart = new ApexCharts(document.querySelector("#monthlyRevenueChart"), monthlyRevenueOptions);
monthlyRevenueChart.render();
// Table Revenue Performance Chart
var tableRevenueData = @json($tableRevenue);
var tableRevenueOptions = {
series: [{
name: 'Pendapatan',
data: tableRevenueData.map(item => item.table_revenue)
}, {
name: 'Jumlah Booking',
data: tableRevenueData.map(item => item.booking_count)
}],
chart: {
type: 'bar',
height: 350,
stacked: false,
toolbar: {
show: false
}
},
plotOptions: {
bar: {
horizontal: true,
barHeight: '70%',
dataLabels: {
position: 'top'
}
}
},
colors: ['#f97316', '#3b82f6'],
dataLabels: {
enabled: true,
formatter: function (val, opts) {
if (opts.seriesIndex === 0) {
return 'Rp' + val.toLocaleString('id-ID');
} else {
return val + ' booking';
}
},
style: {
fontSize: '12px',
colors: ['#333']
},
offsetX: 0
},
stroke: {
width: 1,
colors: ['#fff']
},
xaxis: {
categories: tableRevenueData.map(item => item.table_name),
labels: {
formatter: function (val) {
return val; // Simplified formatter for table names
}
}
},
yaxis: [
{
axisTicks: {
show: true,
},
axisBorder: {
show: true,
color: '#f97316'
},
labels: {
style: {
colors: '#f97316',
},
formatter: function (val) {
return 'Rp' + val.toLocaleString('id-ID');
}
},
title: {
text: "Pendapatan (Rp)",
style: {
color: '#f97316',
}
}
},
{
opposite: true,
axisTicks: {
show: true,
},
axisBorder: {
show: true,
color: '#3b82f6'
},
labels: {
style: {
colors: '#3b82f6',
}
},
title: {
text: "Jumlah Booking",
style: {
color: '#3b82f6',
}
}
}
],
tooltip: {
y: {
formatter: function (val, { seriesIndex }) {
if (seriesIndex === 0) {
return 'Rp' + val.toLocaleString('id-ID');
} else {
return val + ' booking';
}
}
}
},
legend: {
position: 'top',
horizontalAlign: 'left',
offsetY: 10
},
grid: {
borderColor: '#f3f4f6',
strokeDashArray: 5
}
};
var tableRevenueChart = new ApexCharts(document.querySelector("#tableRevenueChart"), tableRevenueOptions);
tableRevenueChart.render();
});
</script>
@endpush

View File

@ -0,0 +1,234 @@
@extends('layouts.admin')
@section('content')
<div class="py-6 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
<div class="bg-gradient-to-r from-blue-500 to-indigo-600 px-6 py-4 flex justify-between items-center">
<h2 class="text-xl font-semibold text-white">Detail Pendapatan Meja: {{ $table->name }}</h2>
<a href="{{ route('admin.revenues.index', ['venue_id' => $table->venue_id, 'start_date' => $startDate, 'end_date' => $endDate]) }}"
class="inline-flex items-center px-3 py-1.5 border border-transparent rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<svg class="h-4 w-4 mr-1.5" 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="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Kembali
</a>
</div>
<div class="p-6">
<!-- Info Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-xl shadow p-6 border border-indigo-100 transform transition-all duration-200 hover:scale-105">
<h3 class="text-sm font-medium text-gray-500 uppercase tracking-wider mb-2">Venue</h3>
<p class="text-xl font-semibold text-gray-800">{{ $table->venue->name }}</p>
</div>
<div class="bg-gradient-to-br from-blue-50 to-cyan-50 rounded-xl shadow p-6 border border-blue-100 transform transition-all duration-200 hover:scale-105">
<h3 class="text-sm font-medium text-gray-500 uppercase tracking-wider mb-2">Total Pendapatan</h3>
<p class="text-xl font-semibold text-blue-600">Rp {{ number_format($totalRevenue, 0, ',', '.') }}</p>
</div>
<div class="bg-gradient-to-br from-green-50 to-emerald-50 rounded-xl shadow p-6 border border-green-100 transform transition-all duration-200 hover:scale-105">
<h3 class="text-sm font-medium text-gray-500 uppercase tracking-wider mb-2">Total Jam Terpakai</h3>
<p class="text-xl font-semibold text-gray-800">{{ $totalHours }} jam</p>
</div>
</div>
<!-- Filter Form -->
<form method="GET" action="{{ route('admin.revenues.detail', $table->id) }}" class="mb-8 bg-gray-50 rounded-lg border border-gray-200 p-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label for="start_date" class="block text-sm font-medium text-gray-700 mb-1">Tanggal Mulai</label>
<input type="date" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
id="start_date" name="start_date" value="{{ $startDate }}">
</div>
<div>
<label for="end_date" class="block text-sm font-medium text-gray-700 mb-1">Tanggal Akhir</label>
<input type="date" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
id="end_date" name="end_date" value="{{ $endDate }}">
</div>
<div class="flex items-end">
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<svg class="h-4 w-4 mr-1.5" 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="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
Filter
</button>
</div>
</div>
</form>
<!-- Bookings Table -->
<div class="mb-8">
<div class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-r from-gray-50 to-gray-100 px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">Daftar Booking</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID Booking</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pelanggan</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Waktu Mulai</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Waktu Selesai</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Durasi (Jam)</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Metode Pembayaran</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Pembayaran</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($bookings as $booking)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $booking->id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $booking->user->name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $booking->start_time->format('d M Y, H:i') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $booking->end_time->format('d M Y, H:i') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
@php
$hours = $booking->start_time->diffInHours($booking->end_time);
$minutes = $booking->start_time->copy()->addHours($hours)->diffInMinutes($booking->end_time);
echo $hours . ($minutes > 0 ? '.' . $minutes : '');
@endphp
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ $booking->payment_method == 'transfer' ? 'bg-blue-100 text-blue-800' :
($booking->payment_method == 'cash' ? 'bg-green-100 text-green-800' :
'bg-purple-100 text-purple-800') }}">
{{ $booking->payment_method }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
Rp {{ number_format($booking->total_amount, 0, ',', '.') }}
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-10 text-center text-sm text-gray-500">
<svg class="mx-auto h-12 w-12 text-gray-400" 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="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="mt-2 font-medium">Tidak ada data booking untuk periode ini</p>
<p class="mt-1 text-gray-400">Coba ubah filter tanggal untuk melihat data lainnya.</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
<!-- Chart: Usage Patterns -->
<div>
<div class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-r from-gray-50 to-gray-100 px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">Pola Penggunaan Meja</h3>
</div>
<div class="p-6">
<canvas id="usagePatternChart" height="300"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Get the booking data
const bookings = @json($bookings);
// Prepare data for usage patterns by hour of day
const hourCounts = Array(24).fill(0);
const dayNames = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
const dayOfWeekCounts = Array(7).fill(0);
bookings.forEach(booking => {
const startTime = new Date(booking.start_time);
hourCounts[startTime.getHours()]++;
dayOfWeekCounts[startTime.getDay()]++;
});
// Create the hour of day chart
const ctx = document.getElementById('usagePatternChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: Array.from({ length: 24 }, (_, i) => `${i}:00`),
datasets: [{
label: 'Jumlah Booking berdasarkan Jam',
data: hourCounts,
backgroundColor: 'rgba(79, 70, 229, 0.7)',
borderColor: 'rgba(79, 70, 229, 1)',
borderWidth: 1,
borderRadius: 4,
barPercentage: 0.7,
categoryPercentage: 0.8
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
labels: {
font: {
family: "'Inter', sans-serif",
size: 12
}
}
},
tooltip: {
backgroundColor: 'rgba(17, 24, 39, 0.8)',
titleFont: {
family: "'Inter', sans-serif",
size: 13
},
bodyFont: {
family: "'Inter', sans-serif",
size: 12
},
callbacks: {
label: function (context) {
const value = context.raw;
return `${value} booking${value !== 1 ? 's' : ''}`;
}
}
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(156, 163, 175, 0.1)'
},
ticks: {
stepSize: 1,
font: {
family: "'Inter', sans-serif",
size: 11
}
}
},
x: {
grid: {
display: false
},
ticks: {
font: {
family: "'Inter', sans-serif",
size: 10
},
maxRotation: 0,
autoSkip: false
}
}
}
}
});
});
</script>
@endsection

View File

@ -0,0 +1,267 @@
@extends('layouts.admin')
@section('content')
<div class="py-6 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
<div class="bg-gradient-to-r from-blue-500 to-indigo-600 px-6 py-4">
<h2 class="text-xl font-semibold text-white">Laporan Pendapatan</h2>
</div>
<div class="p-6">
<!-- Filter Form -->
<form method="GET" action="{{ route('admin.revenues.index') }}" class="mb-8">
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div>
<label for="start_date" class="block text-sm font-medium text-gray-700 mb-1">Tanggal Mulai</label>
<input type="date" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
id="start_date" name="start_date" value="{{ $startDate }}">
</div>
<div>
<label for="end_date" class="block text-sm font-medium text-gray-700 mb-1">Tanggal Akhir</label>
<input type="date" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
id="end_date" name="end_date" value="{{ $endDate }}">
</div>
<div>
<label for="venue_id" class="block text-sm font-medium text-gray-700 mb-1">Venue</label>
<select class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
id="venue_id" name="venue_id">
<option value="">Semua Venue</option>
@foreach($venues as $venue)
<option value="{{ $venue->id }}" {{ $venueId == $venue->id ? 'selected' : '' }}>
{{ $venue->name }}
</option>
@endforeach
</select>
</div>
<div class="flex items-end">
<div class="flex space-x-2">
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<svg class="h-4 w-4 mr-1.5" 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="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
Filter
</button>
<a href="{{ route('admin.revenues.export', request()->query()) }}" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
<svg class="h-4 w-4 mr-1.5" 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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Export
</a>
</div>
</div>
</div>
</form>
<!-- Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow p-6 border border-blue-100">
<div class="text-center">
<h3 class="text-lg font-medium text-gray-700 mb-2">Total Pendapatan</h3>
<p class="text-3xl font-bold text-indigo-600">Rp {{ number_format($totalRevenue, 0, ',', '.') }}</p>
</div>
</div>
<div class="bg-gradient-to-br from-purple-50 to-pink-50 rounded-xl shadow p-6 border border-purple-100">
<div class="text-center">
<h3 class="text-lg font-medium text-gray-700 mb-2">Total Booking</h3>
<p class="text-3xl font-bold text-purple-600">{{ $totalBookings }}</p>
</div>
</div>
</div>
<!-- Chart for Daily Revenue -->
<div class="mb-8">
<div class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-r from-gray-50 to-gray-100 px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">Grafik Pendapatan Harian</h3>
</div>
<div class="p-6">
<canvas id="dailyRevenueChart" height="300"></canvas>
</div>
</div>
</div>
<!-- Revenue Per Venue Table -->
<div class="mb-8">
<div class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-r from-gray-50 to-gray-100 px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">Pendapatan Per Venue</h3>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Venue</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Booking</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Pendapatan</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($revenuePerVenue as $venueRevenue)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $venueRevenue->venue_name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $venueRevenue->total_bookings }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-semibold">
Rp {{ number_format($venueRevenue->total_revenue, 0, ',', '.') }}
</td>
</tr>
@empty
<tr>
<td colspan="3" class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">Tidak ada data pendapatan</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Revenue Per Table -->
@if($revenuePerTable)
<div>
<div class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-r from-gray-50 to-gray-100 px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">Detail Pendapatan Per Meja</h3>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Meja</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Jumlah Booking</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Pendapatan</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($revenuePerTable as $tableRevenue)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $tableRevenue->table_name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $tableRevenue->booking_count }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-semibold">
Rp {{ number_format($tableRevenue->table_revenue, 0, ',', '.') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a href="{{ route('admin.revenues.detail', ['tableId' => $tableRevenue->table_id, 'start_date' => $startDate, 'end_date' => $endDate]) }}"
class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs leading-4 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<svg class="h-4 w-4 mr-1" 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="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
Detail
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">Tidak ada data pendapatan meja</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
@endif
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Prepare data for chart
const dailyData = @json($dailyRevenue);
// Extract dates and revenues
const dates = dailyData.map(item => item.date);
const revenues = dailyData.map(item => item.revenue);
// Create the chart
const ctx = document.getElementById('dailyRevenueChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: dates,
datasets: [{
label: 'Pendapatan Harian (Rp)',
data: revenues,
backgroundColor: 'rgba(79, 70, 229, 0.1)',
borderColor: '#4f46e5',
borderWidth: 2,
tension: 0.3,
pointBackgroundColor: '#4f46e5',
pointBorderColor: '#fff',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
labels: {
font: {
family: "'Inter', sans-serif",
size: 12
}
}
},
tooltip: {
backgroundColor: 'rgba(17, 24, 39, 0.8)',
titleFont: {
family: "'Inter', sans-serif",
size: 13
},
bodyFont: {
family: "'Inter', sans-serif",
size: 12
},
callbacks: {
label: function (context) {
return 'Pendapatan: Rp ' + context.raw.toLocaleString('id-ID');
}
}
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(156, 163, 175, 0.1)'
},
ticks: {
font: {
family: "'Inter', sans-serif",
size: 11
},
callback: function (value) {
return 'Rp ' + value.toLocaleString('id-ID');
}
}
},
x: {
grid: {
display: false
},
ticks: {
font: {
family: "'Inter', sans-serif",
size: 11
}
}
}
}
}
});
});
</script>
@endsection

View File

@ -1,125 +1,151 @@
@extends('layouts.admin')
@section('content')
<div class="p-6 bg-gray-50">
<!-- Header dengan Action Button -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-800">Kelola Meja</h1>
<a href="{{ route('admin.tables.create') }}"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
Tambah Meja Baru
</a>
</div>
<!-- Flash Message -->
@if(session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-6 rounded shadow-md" role="alert">
<div class="flex items-center">
<svg class="h-5 w-5 mr-2" 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 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
<div class="p-6 bg-gray-50">
<!-- Header dengan Action Button -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-800">Kelola Meja</h1>
<a href="{{ route('admin.tables.create') }}"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
clip-rule="evenodd" />
</svg>
<p>{{ session('success') }}</p>
Tambah Meja Baru
</a>
</div>
<!-- Flash Message -->
@if(session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-6 rounded shadow-md" role="alert">
<div class="flex items-center">
<svg class="h-5 w-5 mr-2" 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 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<p>{{ session('success') }}</p>
</div>
</div>
@endif
<!-- Search and Filter -->
<div class="bg-white p-4 rounded-lg shadow-md mb-6">
<form action="{{ route('admin.tables.index') }}" method="GET"
class="md:flex items-center space-y-4 md:space-y-0 md:space-x-4">
<div class="flex-grow">
<input type="text" name="search" value="{{ request('search') }}" placeholder="Cari nama meja..."
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="md:w-1/4">
<select name="status"
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">Semua Status</option>
<option value="Available" {{ request('status') == 'Available' ? 'selected' : '' }}>Available</option>
<option value="Booked" {{ request('status') == 'Booked' ? 'selected' : '' }}>Booked</option>
<option value="Unavailable" {{ request('status') == 'Unavailable' ? 'selected' : '' }}>Unavailable
</option>
</select>
</div>
<div class="flex space-x-2">
<button type="submit"
class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clip-rule="evenodd" />
</svg>
</button>
<a href="{{ route('admin.tables.index') }}"
class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-300 transition-colors duration-300">
Reset
</a>
</div>
</form>
</div>
<!-- Table - dengan wrapper div untuk scroll horizontal pada mobile -->
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-100">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Nama
Meja
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Merek
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Harga
per
Jam</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-600 uppercase tracking-wider">Aksi
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse ($tables as $table)
<tr class="hover:bg-gray-50 transition-colors duration-200">
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium text-gray-900">{{ $table->name }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-gray-700">{{ $table->brand }}</td>
<td class="px-6 py-4 whitespace-nowrap">
<p class="text-sm font-semibold text-gray-500">
Rp. {{ number_format($table['price_per_hour'], 0, ',', '.') }} / jam
</p>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
<div class="flex justify-end space-x-2">
<a href="{{ route('admin.tables.edit', $table->id) }}"
class="text-blue-600 hover:text-blue-900 bg-blue-100 px-3 py-1 rounded-md hover:bg-blue-200 transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</a>
<form action="{{ route('admin.tables.destroy', $table->id) }}" method="POST"
class="inline"
onsubmit="return confirm('Apakah Anda yakin ingin menghapus meja ini?');">
@csrf
@method('DELETE')
<button type="submit"
class="text-red-600 hover:text-red-900 bg-red-100 px-3 py-1 rounded-md hover:bg-red-200 transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-10 text-center text-gray-500">
<div class="flex flex-col items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-400 mb-3" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
<p class="text-lg">Belum ada data meja.</p>
<a href="{{ route('admin.tables.create') }}"
class="mt-3 text-blue-600 hover:underline">Tambah meja sekarang!</a>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endif
<!-- Search and Filter -->
<div class="bg-white p-4 rounded-lg shadow-md mb-6">
<form action="{{ route('admin.tables.index') }}" method="GET" class="md:flex items-center space-y-4 md:space-y-0 md:space-x-4">
<div class="flex-grow">
<input type="text" name="search" value="{{ request('search') }}"
placeholder="Cari nama meja..."
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="md:w-1/4">
<select name="status" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">Semua Status</option>
<option value="Available" {{ request('status') == 'Available' ? 'selected' : '' }}>Available</option>
<option value="Booked" {{ request('status') == 'Booked' ? 'selected' : '' }}>Booked</option>
<option value="Unavailable" {{ request('status') == 'Unavailable' ? 'selected' : '' }}>Unavailable</option>
</select>
</div>
<div class="flex space-x-2">
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
</button>
<a href="{{ route('admin.tables.index') }}" class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-300 transition-colors duration-300">
Reset
</a>
</div>
</form>
<!-- Pagination -->
<div class="mt-6">
{{ $tables->withQueryString()->links() }}
</div>
</div>
<!-- Table -->
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-100">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Nama Meja</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Merek</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-600 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse ($tables as $table)
<tr class="hover:bg-gray-50 transition-colors duration-200">
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium text-gray-900">{{ $table->name }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-gray-700">{{ $table->brand }}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-3 py-1 rounded-full text-xs font-medium
{{ $table->status === 'Available' ? 'bg-green-100 text-green-800' :
($table->status === 'Booked' ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800') }}">
{{ $table->status }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
<div class="flex justify-end space-x-2">
<a href="{{ route('admin.tables.edit', $table->id) }}"
class="text-blue-600 hover:text-blue-900 bg-blue-100 px-3 py-1 rounded-md hover:bg-blue-200 transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</a>
<form action="{{ route('admin.tables.destroy', $table->id) }}" method="POST" class="inline" onsubmit="return confirm('Apakah Anda yakin ingin menghapus meja ini?');">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 bg-red-100 px-3 py-1 rounded-md hover:bg-red-200 transition-colors duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-10 text-center text-gray-500">
<div class="flex flex-col items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-400 mb-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
<p class="text-lg">Belum ada data meja.</p>
<a href="{{ route('admin.tables.create') }}" class="mt-3 text-blue-600 hover:underline">Tambah meja sekarang!</a>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="mt-6">
{{ $tables->withQueryString()->links() }}
</div>
</div>
@endsection

View File

@ -59,14 +59,12 @@ class="fixed inset-y-0 left-0 z-30 w-64 bg-white shadow-lg transition-all durati
<!-- Sidebar Header -->
<div class="flex items-center justify-between h-16 px-4 border-b">
<div class="flex items-center space-x-2">
<div class="p-2 bg-blue-600 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<span class="font-bold text-lg text-gray-800" x-show="sidebarOpen">VenueApp</span>
<span class="font-bold text-lg text-gray-800" x-show="sidebarOpen">Admin</span>
{{-- <div class="p-3 rounded-lg">
<a href="/">
<img src="{{ asset('images/carimeja3.png') }}" alt="carimeja.com" class="w-24">
</a>
</div> --}}
</div>
<button @click="sidebarOpen = !sidebarOpen" class="p-1 rounded-md hover:bg-gray-100 focus:outline-none">
<svg x-show="sidebarOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500"
@ -118,6 +116,15 @@ class="nav-item flex items-center px-3 py-2.5 rounded-lg {{ request()->routeIs('
</svg>
<span x-show="sidebarOpen">Daftar Booking</span>
</a>
<a href="{{ route('admin.revenues.index') }}"
class="nav-item flex items-center px-3 py-2.5 rounded-lg {{ request()->routeIs('admin.bookings.*') ? 'active' : '' }}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span x-show="sidebarOpen">Revenues</span>
</a>
</nav>
<div x-show="sidebarOpen"

View File

@ -55,7 +55,7 @@ class="flex flex-col h-full border border-gray-400 rounded-lg overflow-hidden">
<div class="flex-grow px-4 py-2">
<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>
<p class="text-sm text-gray-500">{{ $venue->address }}</p>
{{-- <p class="text-sm text-gray-500">{{ $venue->address }}</p> --}}
<p class="mt-10 text-gray-500 text-sm">Mulai:
<span class="font-bold text-gray-800">Rp30,000</span>
<span class="text-gray-400 font-thin text-sm">/ jam</span>

View File

@ -91,10 +91,8 @@ class="border rounded-lg shadow-md p-4 mb-4">
<img src="{{ asset('images/meja.jpg') }}" class="w-24">
<div class="ml-4">
<h3 class="font-semibold">{{ $table['name'] }} ({{ $table['brand'] }})</h3>
<p class="text-sm">
<span class="{{ $table['status'] == 'Available' ? 'text-green-600' : 'text-red-600' }}">
{{ $table['status'] }}
</span>
<p class="text-sm font-semibold text-gray-500">
Rp. {{ number_format($table['price_per_hour'], 0, ',', '.') }} / jam
</p>
</div>
</div>
@ -156,7 +154,7 @@ function updateClock() {
// Format functions for pending bookings
function formatDateTime(dateTimeStr) {
// Parse the ISO date string without timezone conversion
// Parse the datetime string
const parts = dateTimeStr.split(/[^0-9]/);
const year = parseInt(parts[0]);
const month = parseInt(parts[1]) - 1; // JS months are 0-based
@ -164,16 +162,20 @@ function formatDateTime(dateTimeStr) {
const hour = parseInt(parts[3]);
const minute = parseInt(parts[4]);
// Gunakan zona waktu Asia/Jakarta (UTC+7)
// Tambahkan 7 jam untuk mengkonversi dari UTC ke WIB
const adjustedHour = (hour + 7) % 24;
const dateFormatter = new Intl.DateTimeFormat('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
});
// Format the date and time separately to avoid timezone issues
// Format the date and time separately
const dateObj = new Date(year, month, day);
return dateFormatter.format(dateObj) + ' ' +
(hour.toString().padStart(2, '0') + ':' +
(adjustedHour.toString().padStart(2, '0') + ':' +
minute.toString().padStart(2, '0'));
}
@ -183,8 +185,11 @@ function formatTime(timeStr) {
const hour = parseInt(parts[3]);
const minute = parseInt(parts[4]);
// Format time manually to avoid timezone issues
return hour.toString().padStart(2, '0') + ':' +
// Tambahkan 7 jam untuk mengkonversi dari UTC ke WIB
const adjustedHour = (hour + 7) % 24;
// Format time manually
return adjustedHour.toString().padStart(2, '0') + ':' +
minute.toString().padStart(2, '0');
}
@ -399,10 +404,10 @@ function formatPrice(price) {
selectedDateTime.setHours(selectedHour, selectedMinute, 0, 0);
// Uncomment this for production to prevent booking past times
// if (selectedDateTime <= now) {
// alert('Jam yang dipilih sudah lewat. Silakan pilih jam yang masih tersedia.');
// return;
// }
if (selectedDateTime <= now) {
alert('Jam yang dipilih sudah lewat. Silakan pilih jam yang masih tersedia.');
return;
}
this.isLoading = true;

View File

@ -3,7 +3,8 @@
@section('content')
<div class="min-h-screen bg-gray-50 flex items-center justify-center px-4 sm:px-6 lg:px-8 py-12">
<div class="w-full max-w-2xl">
<div class="bg-white rounded-xl shadow-2xl overflow-hidden" style="backdrop-filter: blur(20px); background-color: rgba(255, 255, 255, 0.8);">
<div class="bg-white rounded-xl shadow-2xl overflow-hidden"
style="backdrop-filter: blur(20px); background-color: rgba(255, 255, 255, 0.8);">
<div class="p-6 sm:p-10">
<h2 class="text-center text-4xl font-semibold text-gray-900 mb-8">
{{ __('Tambah Admin Baru') }}
@ -15,7 +16,9 @@
@foreach ($errors->all() as $error)
<li class="flex items-center">
<svg class="h-4 w-4 mr-2 text-red-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-11.293a1 1 0 00-1.414-1.414L10 8.586 7.707 6.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 101.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293z" clip-rule="evenodd"/>
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-11.293a1 1 0 00-1.414-1.414L10 8.586 7.707 6.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 101.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293z"
clip-rule="evenodd" />
</svg>
{{ $error }}
</li>
@ -24,12 +27,16 @@
</div>
@endif
<form
action="{{ route('superadmin.admin.store') }}"
method="POST"
x-data="adminForm()"
class="space-y-6"
>
<form action="{{ route('superadmin.admin.store') }}" method="POST" x-data="{
showPassword: false,
showConfirmPassword: false,
togglePassword() {
this.showPassword = !this.showPassword
},
toggleConfirmPassword() {
this.showConfirmPassword = !this.showConfirmPassword
}
}" class="space-y-6">
@csrf
<div class="grid md:grid-cols-2 gap-6">
@ -38,17 +45,10 @@ class="space-y-6"
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
{{ __('Nama Admin') }}
</label>
<input
type="text"
id="name"
name="name"
value="{{ old('name') }}"
required
autocomplete="name"
autofocus
<input type="text" id="name" name="name" value="{{ old('name') }}" required
autocomplete="name" autofocus
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out"
placeholder="Masukkan nama admin"
>
placeholder="Masukkan nama admin">
</div>
{{-- Email --}}
@ -56,15 +56,9 @@ class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
{{ __('Email') }}
</label>
<input
type="email"
id="email"
name="email"
value="{{ old('email') }}"
required
<input type="email" id="email" name="email" value="{{ old('email') }}" required
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out"
placeholder="Masukkan email admin"
>
placeholder="Masukkan email admin">
</div>
</div>
@ -75,26 +69,22 @@ class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:
{{ __('Password') }}
</label>
<div class="relative">
<input
type="password"
id="password"
name="password"
required
x-bind:type="showPassword ? 'text' : 'password'"
<input :type="showPassword ? 'text' : 'password'" id="password" name="password" required
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out"
placeholder="Masukkan password"
>
<button
type="button"
@click="togglePasswordVisibility()"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
>
<svg x-show="!showPassword" class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
placeholder="Masukkan password">
<button type="button" @click="togglePassword()"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5">
<svg x-show="!showPassword" class="h-5 w-5 text-gray-400" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
<svg x-show="showPassword" class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
<svg x-show="showPassword" class="h-5 w-5 text-blue-500" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</button>
</div>
@ -106,26 +96,23 @@ class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
{{ __('Konfirmasi Password') }}
</label>
<div class="relative">
<input
type="password"
id="password_confirmation"
name="password_confirmation"
required
x-bind:type="showConfirmPassword ? 'text' : 'password'"
<input :type="showConfirmPassword ? 'text' : 'password'" id="password_confirmation"
name="password_confirmation" required
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out"
placeholder="Konfirmasi password"
>
<button
type="button"
@click="toggleConfirmPasswordVisibility()"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
>
<svg x-show="!showConfirmPassword" class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
placeholder="Konfirmasi password">
<button type="button" @click="toggleConfirmPassword()"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5">
<svg x-show="!showConfirmPassword" class="h-5 w-5 text-gray-400" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
<svg x-show="showConfirmPassword" class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
<svg x-show="showConfirmPassword" class="h-5 w-5 text-blue-500" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</button>
</div>
@ -138,18 +125,11 @@ class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
<label for="venue_id" class="block text-sm font-medium text-gray-700 mb-2">
{{ __('Venue') }}
</label>
<select
id="venue_id"
name="venue_id"
required
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out"
>
<select id="venue_id" name="venue_id" required
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out">
<option value="">-- Pilih Venue --</option>
@foreach($venues as $venue)
<option
value="{{ $venue->id }}"
{{ old('venue_id') == $venue->id ? 'selected' : '' }}
>
<option value="{{ $venue->id }}" {{ old('venue_id') == $venue->id ? 'selected' : '' }}>
{{ $venue->name }}
</option>
@endforeach
@ -161,12 +141,8 @@ class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:
<label for="role" class="block text-sm font-medium text-gray-700 mb-2">
{{ __('Role') }}
</label>
<select
id="role"
name="role"
required
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out"
>
<select id="role" name="role" required
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300 ease-in-out">
<option value="admin" {{ old('role') == 'admin' ? 'selected' : '' }}>
Admin
</option>
@ -179,16 +155,12 @@ class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:
{{-- Tombol Aksi --}}
<div class="flex justify-end space-x-4 pt-6">
<a
href="{{ route('superadmin.admin.index') }}"
class="px-6 py-3 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg font-medium transition duration-300 ease-in-out"
>
<a href="{{ route('superadmin.admin.index') }}"
class="px-6 py-3 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg font-medium transition duration-300 ease-in-out">
{{ __('Batal') }}
</a>
<button
type="submit"
class="px-6 py-3 bg-blue-500 text-white rounded-lg font-medium hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-300 ease-in-out"
>
<button type="submit"
class="px-6 py-3 bg-blue-500 text-white rounded-lg font-medium hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-300 ease-in-out">
{{ __('Simpan') }}
</button>
</div>
@ -199,20 +171,6 @@ class="px-6 py-3 bg-blue-500 text-white rounded-lg font-medium hover:bg-blue-600
</div>
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script>
function adminForm() {
return {
showPassword: false,
showConfirmPassword: false,
togglePasswordVisibility() {
this.showPassword = !this.showPassword;
},
toggleConfirmPasswordVisibility() {
this.showConfirmPassword = !this.showConfirmPassword;
}
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
@endpush
@endsection

View File

@ -7,6 +7,7 @@
use App\Http\Controllers\pages\BookingHistoryController;
use App\Http\Controllers\admin\BookingsController;
use App\Http\Controllers\admin\TableController;
use App\Http\Controllers\admin\RevenueController;
use App\Http\Controllers\admin\AdminController;
use App\Http\Controllers\Auth\VerificationController;
use App\Http\Controllers\superadmin\SuperAdminController;
@ -81,6 +82,11 @@
Route::get('/tables/{id}/edit', [TableController::class, 'edit'])->name('admin.tables.edit');
Route::put('/tables/{id}', [TableController::class, 'update'])->name('admin.tables.update');
Route::delete('/tables/{id}', [TableController::class, 'destroy'])->name('admin.tables.destroy');
// CRUD routes untuk revenue
Route::get('/revenues', [RevenueController::class, 'index'])->name('admin.revenues.index');
Route::get('/revenues/detail/{tableId}', [RevenueController::class, 'detail'])->name('admin.revenues.detail');
Route::get('/revenues/export', [RevenueController::class, 'export'])->name('admin.revenues.export');
});
// Superadmin routes