Revenues Admin
This commit is contained in:
parent
1ccfbfa336
commit
1429115bf8
|
@ -8,12 +8,35 @@
|
||||||
use App\Models\Table;
|
use App\Models\Table;
|
||||||
use App\Models\Booking;
|
use App\Models\Booking;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class AdminController extends Controller
|
class AdminController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
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
|
// Menghitung booking hari ini
|
||||||
$todayBookings = Booking::whereDate('created_at', now())
|
$todayBookings = Booking::whereDate('created_at', now())
|
||||||
|
@ -32,7 +55,7 @@ public function index()
|
||||||
->whereHas('table', function ($query) use ($venue) {
|
->whereHas('table', function ($query) use ($venue) {
|
||||||
$query->where('venue_id', $venue->id);
|
$query->where('venue_id', $venue->id);
|
||||||
})
|
})
|
||||||
->where('status', 'paid')
|
->where('bookings.status', 'paid')
|
||||||
->sum('total_amount');
|
->sum('total_amount');
|
||||||
|
|
||||||
// Menghitung pendapatan bulan ini
|
// Menghitung pendapatan bulan ini
|
||||||
|
@ -41,20 +64,20 @@ public function index()
|
||||||
->whereHas('table', function ($query) use ($venue) {
|
->whereHas('table', function ($query) use ($venue) {
|
||||||
$query->where('venue_id', $venue->id);
|
$query->where('venue_id', $venue->id);
|
||||||
})
|
})
|
||||||
->where('status', 'paid')
|
->where('bookings.status', 'paid')
|
||||||
->sum('total_amount');
|
->sum('total_amount');
|
||||||
|
|
||||||
// Menghitung jumlah booking berdasarkan status
|
// Menghitung jumlah booking berdasarkan status
|
||||||
$pendingBookings = Booking::whereHas('table', function ($query) use ($venue) {
|
$pendingBookings = Booking::whereHas('table', function ($query) use ($venue) {
|
||||||
$query->where('venue_id', $venue->id);
|
$query->where('venue_id', $venue->id);
|
||||||
})
|
})
|
||||||
->where('status', 'pending')
|
->where('bookings.status', 'pending')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
$paidBookings = Booking::whereHas('table', function ($query) use ($venue) {
|
$paidBookings = Booking::whereHas('table', function ($query) use ($venue) {
|
||||||
$query->where('venue_id', $venue->id);
|
$query->where('venue_id', $venue->id);
|
||||||
})
|
})
|
||||||
->where('status', 'paid')
|
->where('bookings.status', 'paid')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
// Ambil booking terbaru
|
// Ambil booking terbaru
|
||||||
|
@ -66,7 +89,7 @@ public function index()
|
||||||
->with(['user', 'table'])
|
->with(['user', 'table'])
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
// Menghitung data analitik untuk diagram
|
// Menghitung data analitik untuk diagram booking 7 hari terakhir
|
||||||
$lastWeekBookings = [];
|
$lastWeekBookings = [];
|
||||||
for ($i = 6; $i >= 0; $i--) {
|
for ($i = 6; $i >= 0; $i--) {
|
||||||
$date = now()->subDays($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(
|
return view('admin.dashboard', compact(
|
||||||
'venue',
|
'venue',
|
||||||
|
'venues',
|
||||||
|
'isSuperAdmin',
|
||||||
'todayBookings',
|
'todayBookings',
|
||||||
'totalTables',
|
'totalTables',
|
||||||
'usedTables',
|
'usedTables',
|
||||||
|
@ -92,7 +225,10 @@ public function index()
|
||||||
'monthlyRevenue',
|
'monthlyRevenue',
|
||||||
'pendingBookings',
|
'pendingBookings',
|
||||||
'paidBookings',
|
'paidBookings',
|
||||||
'lastWeekBookings'
|
'lastWeekBookings',
|
||||||
|
'lastSixMonthsRevenue',
|
||||||
|
'tableRevenue'
|
||||||
|
// Hapus 'revenueByDay' dari compact
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,17 @@ public function isAdmin()
|
||||||
return $this->role === 'admin';
|
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.
|
* Get the venue that the admin belongs to.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'Asia/Jakarta',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -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">
|
<div class="w-full md:w-1/4">
|
||||||
<label for="status" class="block text-sm font-medium text-gray-700 mb-1">Status</label>
|
<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="">Semua Status</option>
|
||||||
<option value="booked" {{ request('status') == 'booked' ? 'selected' : '' }}>Booked</option>
|
<option value="booked" {{ request('status') == 'booked' ? 'selected' : '' }}>Booked</option>
|
||||||
<option value="selesai" {{ request('status') == 'selesai' ? 'selected' : '' }}>Selesai</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>
|
</select>
|
||||||
</div>
|
</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">
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
<thead class="bg-gray-50">
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col"
|
||||||
<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">
|
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
|
User
|
||||||
@if(request('sort') == 'user')
|
@if(request('sort') == 'user')
|
||||||
<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>
|
||||||
|
@ -92,8 +96,10 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col"
|
||||||
<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">
|
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
|
Meja
|
||||||
@if(request('sort') == 'table')
|
@if(request('sort') == 'table')
|
||||||
<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>
|
||||||
|
@ -102,8 +108,10 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col"
|
||||||
<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">
|
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
|
Mulai
|
||||||
@if(request('sort') == 'start_time')
|
@if(request('sort') == 'start_time')
|
||||||
<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>
|
||||||
|
@ -112,8 +120,10 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col"
|
||||||
<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">
|
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
|
Selesai
|
||||||
@if(request('sort') == 'end_time')
|
@if(request('sort') == 'end_time')
|
||||||
<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>
|
||||||
|
@ -122,19 +132,22 @@ class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
{{-- <th scope="col"
|
||||||
<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">
|
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
|
Status
|
||||||
@if(request('sort') == '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
|
@else
|
||||||
<i class="fas fa-sort ml-1 opacity-50"></i>
|
<i class="fas fa-sort ml-1 opacity-50"></i>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th> --}}
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
{{-- <th scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Aksi
|
Aksi
|
||||||
</th>
|
</th> --}}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
<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">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0 h-10 w-10">
|
<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>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<div class="text-sm font-medium text-gray-900">{{ $booking->user->name }}</div>
|
<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>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<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 font-medium text-gray-900">{{ $booking->table->name }}</div>
|
||||||
<div class="text-sm text-gray-500">Kapasitas: {{ $booking->table->capacity }} orang</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<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-900">
|
||||||
<div class="text-sm text-gray-500">{{ \Carbon\Carbon::parse($booking->start_time)->format('d M Y') }}</div>
|
{{ \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>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<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-900">
|
||||||
<div class="text-sm text-gray-500">{{ \Carbon\Carbon::parse($booking->end_time)->format('d M Y') }}</div>
|
{{ \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>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
{{-- <td class="px-6 py-4 whitespace-nowrap">
|
||||||
@if($booking->status === 'booked')
|
@if($booking->status === 'booked')
|
||||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
|
<span
|
||||||
<i class="fas fa-clock mr-1"></i> Booked
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||||
</span>
|
<i class="fas fa-clock mr-1"></i> Booked
|
||||||
|
</span>
|
||||||
@elseif($booking->status === 'selesai')
|
@elseif($booking->status === 'selesai')
|
||||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
<span
|
||||||
<i class="fas fa-check mr-1"></i> Selesai
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||||
</span>
|
<i class="fas fa-check mr-1"></i> Selesai
|
||||||
|
</span>
|
||||||
@else
|
@else
|
||||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
<span
|
||||||
<i class="fas fa-times mr-1"></i> Dibatalkan
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
||||||
</span>
|
<i class="fas fa-times mr-1"></i> Dibatalkan
|
||||||
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td> --}}
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
{{-- <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
<div class="flex space-x-2">
|
<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>
|
<i class="fas fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@if($booking->status === 'booked')
|
@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>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</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
|
@csrf
|
||||||
@method('PATCH')
|
@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>
|
<i class="fas fa-check-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</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
|
@csrf
|
||||||
@method('PATCH')
|
@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>
|
<i class="fas fa-ban"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td> --}}
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<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="px-4 py-3 bg-gray-50 border-t border-gray-200">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="text-sm text-gray-500">
|
<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>
|
||||||
<div>
|
<div>
|
||||||
{{ $bookings->appends(request()->query())->links() }}
|
{{ $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')
|
@push('scripts')
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// Date range validation
|
// Date range validation
|
||||||
const dateFrom = document.getElementById('date_from');
|
const dateFrom = document.getElementById('date_from');
|
||||||
const dateTo = document.getElementById('date_to');
|
const dateTo = document.getElementById('date_to');
|
||||||
|
|
||||||
if (dateFrom && dateTo) {
|
if (dateFrom && dateTo) {
|
||||||
dateFrom.addEventListener('change', function() {
|
dateFrom.addEventListener('change', function () {
|
||||||
dateTo.min = dateFrom.value;
|
dateTo.min = dateFrom.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
dateTo.addEventListener('change', function() {
|
dateTo.addEventListener('change', function () {
|
||||||
dateFrom.max = dateTo.value;
|
dateFrom.max = dateTo.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,8 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-500">Penggunaan Meja</p>
|
<p class="text-sm font-medium text-gray-500">Penggunaan Meja</p>
|
||||||
<p class="text-2xl font-bold text-gray-800">
|
<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>
|
||||||
<div class="text-amber-500 p-2 bg-amber-50 rounded-lg">
|
<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"
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Charts and Recent Bookings -->
|
<!-- Charts Row 1 -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||||
<!-- Weekly Bookings Chart -->
|
<!-- Weekly Bookings Chart -->
|
||||||
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm p-6">
|
<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>
|
<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>
|
<p class="font-medium text-gray-800">{{ $booking->user->name }}</p>
|
||||||
<div class="flex items-center text-sm text-gray-500">
|
<div class="flex items-center text-sm text-gray-500">
|
||||||
<span class="mr-2">{{ $booking->table->name }}</span>
|
<span class="mr-2">{{ $booking->table->name }}</span>
|
||||||
<span class="text-xs px-2 py-0.5 rounded-full {{
|
<span
|
||||||
$booking->status === 'paid' ? 'bg-green-100 text-green-800' :
|
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' :
|
($booking->status === 'pending' ? 'bg-amber-100 text-amber-800' :
|
||||||
'bg-gray-100 text-gray-800')
|
'bg-gray-100 text-gray-800')
|
||||||
}}">
|
}}">
|
||||||
{{ ucfirst($booking->status) }}
|
{{ ucfirst($booking->status) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -158,6 +160,28 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -224,6 +248,209 @@ class="font-semibold text-red-500">{{ $usedTables }}</span></p>
|
||||||
|
|
||||||
var chart = new ApexCharts(document.querySelector("#bookingsChart"), options);
|
var chart = new ApexCharts(document.querySelector("#bookingsChart"), options);
|
||||||
chart.render();
|
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>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,125 +1,151 @@
|
||||||
@extends('layouts.admin')
|
@extends('layouts.admin')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="p-6 bg-gray-50">
|
<div class="p-6 bg-gray-50">
|
||||||
<!-- Header dengan Action Button -->
|
<!-- Header dengan Action Button -->
|
||||||
<div class="flex justify-between items-center mb-6">
|
<div class="flex justify-between items-center mb-6">
|
||||||
<h1 class="text-3xl font-bold text-gray-800">Kelola Meja</h1>
|
<h1 class="text-3xl font-bold text-gray-800">Kelola Meja</h1>
|
||||||
<a href="{{ route('admin.tables.create') }}"
|
<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">
|
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">
|
<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" />
|
<path fill-rule="evenodd"
|
||||||
</svg>
|
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"
|
||||||
Tambah Meja Baru
|
clip-rule="evenodd" />
|
||||||
</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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
|
|
||||||
<!-- Search and Filter -->
|
<!-- Pagination -->
|
||||||
<div class="bg-white p-4 rounded-lg shadow-md mb-6">
|
<div class="mt-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">
|
{{ $tables->withQueryString()->links() }}
|
||||||
<div class="flex-grow">
|
</div>
|
||||||
<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>
|
</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
|
@endsection
|
|
@ -59,14 +59,12 @@ class="fixed inset-y-0 left-0 z-30 w-64 bg-white shadow-lg transition-all durati
|
||||||
<!-- Sidebar Header -->
|
<!-- Sidebar Header -->
|
||||||
<div class="flex items-center justify-between h-16 px-4 border-b">
|
<div class="flex items-center justify-between h-16 px-4 border-b">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="p-2 bg-blue-600 rounded-lg">
|
<span class="font-bold text-lg text-gray-800" x-show="sidebarOpen">Admin</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none"
|
{{-- <div class="p-3 rounded-lg">
|
||||||
viewBox="0 0 24 24" stroke="currentColor">
|
<a href="/">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<img src="{{ asset('images/carimeja3.png') }}" alt="carimeja.com" class="w-24">
|
||||||
d="M13 10V3L4 14h7v7l9-11h-7z" />
|
</a>
|
||||||
</svg>
|
</div> --}}
|
||||||
</div>
|
|
||||||
<span class="font-bold text-lg text-gray-800" x-show="sidebarOpen">VenueApp</span>
|
|
||||||
</div>
|
</div>
|
||||||
<button @click="sidebarOpen = !sidebarOpen" class="p-1 rounded-md hover:bg-gray-100 focus:outline-none">
|
<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"
|
<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>
|
</svg>
|
||||||
<span x-show="sidebarOpen">Daftar Booking</span>
|
<span x-show="sidebarOpen">Daftar Booking</span>
|
||||||
</a>
|
</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>
|
</nav>
|
||||||
|
|
||||||
<div x-show="sidebarOpen"
|
<div x-show="sidebarOpen"
|
||||||
|
|
|
@ -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">
|
<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>
|
||||||
<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:
|
<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>
|
||||||
|
|
|
@ -91,10 +91,8 @@ class="border rounded-lg shadow-md p-4 mb-4">
|
||||||
<img src="{{ asset('images/meja.jpg') }}" class="w-24">
|
<img src="{{ asset('images/meja.jpg') }}" class="w-24">
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<h3 class="font-semibold">{{ $table['name'] }} ({{ $table['brand'] }})</h3>
|
<h3 class="font-semibold">{{ $table['name'] }} ({{ $table['brand'] }})</h3>
|
||||||
<p class="text-sm">
|
<p class="text-sm font-semibold text-gray-500">
|
||||||
<span class="{{ $table['status'] == 'Available' ? 'text-green-600' : 'text-red-600' }}">
|
Rp. {{ number_format($table['price_per_hour'], 0, ',', '.') }} / jam
|
||||||
{{ $table['status'] }}
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -156,7 +154,7 @@ function updateClock() {
|
||||||
|
|
||||||
// Format functions for pending bookings
|
// Format functions for pending bookings
|
||||||
function formatDateTime(dateTimeStr) {
|
function formatDateTime(dateTimeStr) {
|
||||||
// Parse the ISO date string without timezone conversion
|
// Parse the datetime string
|
||||||
const parts = dateTimeStr.split(/[^0-9]/);
|
const parts = dateTimeStr.split(/[^0-9]/);
|
||||||
const year = parseInt(parts[0]);
|
const year = parseInt(parts[0]);
|
||||||
const month = parseInt(parts[1]) - 1; // JS months are 0-based
|
const month = parseInt(parts[1]) - 1; // JS months are 0-based
|
||||||
|
@ -164,16 +162,20 @@ function formatDateTime(dateTimeStr) {
|
||||||
const hour = parseInt(parts[3]);
|
const hour = parseInt(parts[3]);
|
||||||
const minute = parseInt(parts[4]);
|
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', {
|
const dateFormatter = new Intl.DateTimeFormat('id-ID', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
year: 'numeric',
|
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);
|
const dateObj = new Date(year, month, day);
|
||||||
return dateFormatter.format(dateObj) + ' ' +
|
return dateFormatter.format(dateObj) + ' ' +
|
||||||
(hour.toString().padStart(2, '0') + ':' +
|
(adjustedHour.toString().padStart(2, '0') + ':' +
|
||||||
minute.toString().padStart(2, '0'));
|
minute.toString().padStart(2, '0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,8 +185,11 @@ function formatTime(timeStr) {
|
||||||
const hour = parseInt(parts[3]);
|
const hour = parseInt(parts[3]);
|
||||||
const minute = parseInt(parts[4]);
|
const minute = parseInt(parts[4]);
|
||||||
|
|
||||||
// Format time manually to avoid timezone issues
|
// Tambahkan 7 jam untuk mengkonversi dari UTC ke WIB
|
||||||
return hour.toString().padStart(2, '0') + ':' +
|
const adjustedHour = (hour + 7) % 24;
|
||||||
|
|
||||||
|
// Format time manually
|
||||||
|
return adjustedHour.toString().padStart(2, '0') + ':' +
|
||||||
minute.toString().padStart(2, '0');
|
minute.toString().padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,10 +404,10 @@ function formatPrice(price) {
|
||||||
selectedDateTime.setHours(selectedHour, selectedMinute, 0, 0);
|
selectedDateTime.setHours(selectedHour, selectedMinute, 0, 0);
|
||||||
|
|
||||||
// Uncomment this for production to prevent booking past times
|
// Uncomment this for production to prevent booking past times
|
||||||
// if (selectedDateTime <= now) {
|
if (selectedDateTime <= now) {
|
||||||
// alert('Jam yang dipilih sudah lewat. Silakan pilih jam yang masih tersedia.');
|
alert('Jam yang dipilih sudah lewat. Silakan pilih jam yang masih tersedia.');
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
@section('content')
|
@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="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="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">
|
<div class="p-6 sm:p-10">
|
||||||
<h2 class="text-center text-4xl font-semibold text-gray-900 mb-8">
|
<h2 class="text-center text-4xl font-semibold text-gray-900 mb-8">
|
||||||
{{ __('Tambah Admin Baru') }}
|
{{ __('Tambah Admin Baru') }}
|
||||||
|
@ -15,7 +16,9 @@
|
||||||
@foreach ($errors->all() as $error)
|
@foreach ($errors->all() as $error)
|
||||||
<li class="flex items-center">
|
<li class="flex items-center">
|
||||||
<svg class="h-4 w-4 mr-2 text-red-500" fill="currentColor" viewBox="0 0 20 20">
|
<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>
|
</svg>
|
||||||
{{ $error }}
|
{{ $error }}
|
||||||
</li>
|
</li>
|
||||||
|
@ -24,12 +27,16 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<form
|
<form action="{{ route('superadmin.admin.store') }}" method="POST" x-data="{
|
||||||
action="{{ route('superadmin.admin.store') }}"
|
showPassword: false,
|
||||||
method="POST"
|
showConfirmPassword: false,
|
||||||
x-data="adminForm()"
|
togglePassword() {
|
||||||
class="space-y-6"
|
this.showPassword = !this.showPassword
|
||||||
>
|
},
|
||||||
|
toggleConfirmPassword() {
|
||||||
|
this.showConfirmPassword = !this.showConfirmPassword
|
||||||
|
}
|
||||||
|
}" class="space-y-6">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="grid md:grid-cols-2 gap-6">
|
<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">
|
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
{{ __('Nama Admin') }}
|
{{ __('Nama Admin') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input type="text" id="name" name="name" value="{{ old('name') }}" required
|
||||||
type="text"
|
autocomplete="name" autofocus
|
||||||
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"
|
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>
|
</div>
|
||||||
|
|
||||||
{{-- Email --}}
|
{{-- 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">
|
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
{{ __('Email') }}
|
{{ __('Email') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input type="email" id="email" name="email" value="{{ old('email') }}" required
|
||||||
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"
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -75,26 +69,22 @@ class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:
|
||||||
{{ __('Password') }}
|
{{ __('Password') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input :type="showPassword ? 'text' : 'password'" id="password" name="password" required
|
||||||
type="password"
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
required
|
|
||||||
x-bind:type="showPassword ? 'text' : 'password'"
|
|
||||||
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"
|
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"
|
placeholder="Masukkan password">
|
||||||
>
|
<button type="button" @click="togglePassword()"
|
||||||
<button
|
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5">
|
||||||
type="button"
|
<svg x-show="!showPassword" class="h-5 w-5 text-gray-400" fill="none"
|
||||||
@click="togglePasswordVisibility()"
|
stroke="currentColor" viewBox="0 0 24 24">
|
||||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
|
<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 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>
|
||||||
<svg x-show="showPassword" class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg x-show="showPassword" class="h-5 w-5 text-blue-500" fill="none"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<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" />
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,26 +96,23 @@ class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
|
||||||
{{ __('Konfirmasi Password') }}
|
{{ __('Konfirmasi Password') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input :type="showConfirmPassword ? 'text' : 'password'" id="password_confirmation"
|
||||||
type="password"
|
name="password_confirmation" required
|
||||||
id="password_confirmation"
|
|
||||||
name="password_confirmation"
|
|
||||||
required
|
|
||||||
x-bind:type="showConfirmPassword ? 'text' : 'password'"
|
|
||||||
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"
|
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"
|
placeholder="Konfirmasi password">
|
||||||
>
|
<button type="button" @click="toggleConfirmPassword()"
|
||||||
<button
|
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5">
|
||||||
type="button"
|
<svg x-show="!showConfirmPassword" class="h-5 w-5 text-gray-400" fill="none"
|
||||||
@click="toggleConfirmPasswordVisibility()"
|
stroke="currentColor" viewBox="0 0 24 24">
|
||||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
|
<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 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>
|
||||||
<svg x-show="showConfirmPassword" class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg x-show="showConfirmPassword" class="h-5 w-5 text-blue-500" fill="none"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<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" />
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<label for="venue_id" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
{{ __('Venue') }}
|
{{ __('Venue') }}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select id="venue_id" name="venue_id" required
|
||||||
id="venue_id"
|
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">
|
||||||
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>
|
<option value="">-- Pilih Venue --</option>
|
||||||
@foreach($venues as $venue)
|
@foreach($venues as $venue)
|
||||||
<option
|
<option value="{{ $venue->id }}" {{ old('venue_id') == $venue->id ? 'selected' : '' }}>
|
||||||
value="{{ $venue->id }}"
|
|
||||||
{{ old('venue_id') == $venue->id ? 'selected' : '' }}
|
|
||||||
>
|
|
||||||
{{ $venue->name }}
|
{{ $venue->name }}
|
||||||
</option>
|
</option>
|
||||||
@endforeach
|
@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">
|
<label for="role" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
{{ __('Role') }}
|
{{ __('Role') }}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select id="role" name="role" required
|
||||||
id="role"
|
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">
|
||||||
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' : '' }}>
|
<option value="admin" {{ old('role') == 'admin' ? 'selected' : '' }}>
|
||||||
Admin
|
Admin
|
||||||
</option>
|
</option>
|
||||||
|
@ -179,16 +155,12 @@ class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:
|
||||||
|
|
||||||
{{-- Tombol Aksi --}}
|
{{-- Tombol Aksi --}}
|
||||||
<div class="flex justify-end space-x-4 pt-6">
|
<div class="flex justify-end space-x-4 pt-6">
|
||||||
<a
|
<a href="{{ route('superadmin.admin.index') }}"
|
||||||
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">
|
||||||
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') }}
|
{{ __('Batal') }}
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button type="submit"
|
||||||
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">
|
||||||
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') }}
|
{{ __('Simpan') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -199,20 +171,6 @@ class="px-6 py-3 bg-blue-500 text-white rounded-lg font-medium hover:bg-blue-600
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
<script>
|
|
||||||
function adminForm() {
|
|
||||||
return {
|
|
||||||
showPassword: false,
|
|
||||||
showConfirmPassword: false,
|
|
||||||
togglePasswordVisibility() {
|
|
||||||
this.showPassword = !this.showPassword;
|
|
||||||
},
|
|
||||||
toggleConfirmPasswordVisibility() {
|
|
||||||
this.showConfirmPassword = !this.showConfirmPassword;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@endpush
|
@endpush
|
||||||
@endsection
|
@endsection
|
|
@ -7,6 +7,7 @@
|
||||||
use App\Http\Controllers\pages\BookingHistoryController;
|
use App\Http\Controllers\pages\BookingHistoryController;
|
||||||
use App\Http\Controllers\admin\BookingsController;
|
use App\Http\Controllers\admin\BookingsController;
|
||||||
use App\Http\Controllers\admin\TableController;
|
use App\Http\Controllers\admin\TableController;
|
||||||
|
use App\Http\Controllers\admin\RevenueController;
|
||||||
use App\Http\Controllers\admin\AdminController;
|
use App\Http\Controllers\admin\AdminController;
|
||||||
use App\Http\Controllers\Auth\VerificationController;
|
use App\Http\Controllers\Auth\VerificationController;
|
||||||
use App\Http\Controllers\superadmin\SuperAdminController;
|
use App\Http\Controllers\superadmin\SuperAdminController;
|
||||||
|
@ -81,6 +82,11 @@
|
||||||
Route::get('/tables/{id}/edit', [TableController::class, 'edit'])->name('admin.tables.edit');
|
Route::get('/tables/{id}/edit', [TableController::class, 'edit'])->name('admin.tables.edit');
|
||||||
Route::put('/tables/{id}', [TableController::class, 'update'])->name('admin.tables.update');
|
Route::put('/tables/{id}', [TableController::class, 'update'])->name('admin.tables.update');
|
||||||
Route::delete('/tables/{id}', [TableController::class, 'destroy'])->name('admin.tables.destroy');
|
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
|
// Superadmin routes
|
||||||
|
|
Loading…
Reference in New Issue