Tampilan dan fitur halaman admin
This commit is contained in:
parent
d4305a4f61
commit
2e9086a761
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\Booking;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
|
||||
class BookingsExport implements FromCollection, WithHeadings, WithMapping, WithStyles, ShouldAutoSize
|
||||
{
|
||||
protected $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function collection()
|
||||
{
|
||||
$query = Booking::with(['table', 'user']);
|
||||
|
||||
// Apply the same filters as in the controller
|
||||
if ($this->request->has('search') && !empty($this->request->search)) {
|
||||
$search = $this->request->search;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->whereHas('user', function ($query) use ($search) {
|
||||
$query->where('name', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
})->orWhereHas('table', function ($query) use ($search) {
|
||||
$query->where('name', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->request->has('status') && !empty($this->request->status)) {
|
||||
$query->where('status', $this->request->status);
|
||||
}
|
||||
|
||||
if ($this->request->has('date_from') && !empty($this->request->date_from)) {
|
||||
$dateFrom = Carbon::parse($this->request->date_from)->startOfDay();
|
||||
$query->where('start_time', '>=', $dateFrom);
|
||||
}
|
||||
|
||||
if ($this->request->has('date_to') && !empty($this->request->date_to)) {
|
||||
$dateTo = Carbon::parse($this->request->date_to)->endOfDay();
|
||||
$query->where('start_time', '<=', $dateTo);
|
||||
}
|
||||
|
||||
// Sort by start time by default
|
||||
$sortColumn = $this->request->sort ?? 'start_time';
|
||||
$sortDirection = $this->request->direction ?? 'desc';
|
||||
|
||||
return $query->orderBy($sortColumn, $sortDirection)->get();
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'ID',
|
||||
'User',
|
||||
'Email',
|
||||
'Meja',
|
||||
'Kapasitas',
|
||||
'Mulai',
|
||||
'Selesai',
|
||||
'Durasi (jam)',
|
||||
'Status',
|
||||
'Dibuat Pada',
|
||||
];
|
||||
}
|
||||
|
||||
public function map($booking): array
|
||||
{
|
||||
$startTime = Carbon::parse($booking->start_time);
|
||||
$endTime = Carbon::parse($booking->end_time);
|
||||
$duration = $startTime->diffInHours($endTime);
|
||||
|
||||
return [
|
||||
$booking->id,
|
||||
$booking->user->name,
|
||||
$booking->user->email,
|
||||
$booking->table->name,
|
||||
$booking->table->capacity . ' orang',
|
||||
$startTime->format('d/m/Y H:i'),
|
||||
$endTime->format('d/m/Y H:i'),
|
||||
$duration,
|
||||
ucfirst($booking->status),
|
||||
Carbon::parse($booking->created_at)->format('d/m/Y H:i'),
|
||||
];
|
||||
}
|
||||
|
||||
public function styles(Worksheet $sheet)
|
||||
{
|
||||
return [
|
||||
// Style the first row (headers)
|
||||
1 => [
|
||||
'font' => ['bold' => true],
|
||||
'fill' => [
|
||||
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
|
||||
'startColor' => ['argb' => 'FFE0E0E0'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -15,16 +15,49 @@ public function index()
|
|||
{
|
||||
$venue = Venue::find(auth()->user()->venue_id);
|
||||
|
||||
// Menghitung booking hari ini
|
||||
$todayBookings = Booking::whereDate('created_at', now())
|
||||
->whereHas('table', function ($query) use ($venue) {
|
||||
$query->where('venue_id', $venue->id);
|
||||
})
|
||||
->count();
|
||||
|
||||
// Kalkulasi status meja
|
||||
$totalTables = Table::where('venue_id', $venue->id)->count();
|
||||
$usedTables = Table::where('venue_id', $venue->id)->where('status', 'booked')->count();
|
||||
$availableTables = Table::where('venue_id', $venue->id)->where('status', 'available')->count();
|
||||
|
||||
// Menghitung pendapatan hari ini
|
||||
$todayRevenue = Booking::whereDate('created_at', now())
|
||||
->whereHas('table', function ($query) use ($venue) {
|
||||
$query->where('venue_id', $venue->id);
|
||||
})
|
||||
->where('status', 'paid')
|
||||
->sum('total_amount');
|
||||
|
||||
// Menghitung pendapatan bulan ini
|
||||
$monthlyRevenue = Booking::whereMonth('created_at', now()->month)
|
||||
->whereYear('created_at', now()->year)
|
||||
->whereHas('table', function ($query) use ($venue) {
|
||||
$query->where('venue_id', $venue->id);
|
||||
})
|
||||
->where('status', 'paid')
|
||||
->sum('total_amount');
|
||||
|
||||
// Menghitung jumlah booking berdasarkan status
|
||||
$pendingBookings = Booking::whereHas('table', function ($query) use ($venue) {
|
||||
$query->where('venue_id', $venue->id);
|
||||
})
|
||||
->where('status', 'pending')
|
||||
->count();
|
||||
|
||||
$paidBookings = Booking::whereHas('table', function ($query) use ($venue) {
|
||||
$query->where('venue_id', $venue->id);
|
||||
})
|
||||
->where('status', 'paid')
|
||||
->count();
|
||||
|
||||
// Ambil booking terbaru
|
||||
$recentBookings = Booking::whereHas('table', function ($query) use ($venue) {
|
||||
$query->where('venue_id', $venue->id);
|
||||
})
|
||||
|
@ -33,13 +66,33 @@ public function index()
|
|||
->with(['user', 'table'])
|
||||
->get();
|
||||
|
||||
// Menghitung data analitik untuk diagram
|
||||
$lastWeekBookings = [];
|
||||
for ($i = 6; $i >= 0; $i--) {
|
||||
$date = now()->subDays($i);
|
||||
$count = Booking::whereDate('created_at', $date)
|
||||
->whereHas('table', function ($query) use ($venue) {
|
||||
$query->where('venue_id', $venue->id);
|
||||
})
|
||||
->count();
|
||||
$lastWeekBookings[] = [
|
||||
'date' => $date->format('d/m'),
|
||||
'count' => $count
|
||||
];
|
||||
}
|
||||
|
||||
return view('admin.dashboard', compact(
|
||||
'venue',
|
||||
'todayBookings',
|
||||
'totalTables',
|
||||
'usedTables',
|
||||
'availableTables',
|
||||
'recentBookings'
|
||||
'recentBookings',
|
||||
'todayRevenue',
|
||||
'monthlyRevenue',
|
||||
'pendingBookings',
|
||||
'paidBookings',
|
||||
'lastWeekBookings'
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,20 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\admin;
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Booking;
|
||||
use App\Models\Table;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Exports\BookingsExport;
|
||||
|
||||
class BookingsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$bookings = Booking::with(['table', 'user'])
|
||||
->orderBy('start_time', 'desc')
|
||||
->paginate(10);
|
||||
$query = Booking::with(['table', 'user']);
|
||||
|
||||
// Search functionality
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$search = $request->search;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->whereHas('user', function ($query) use ($search) {
|
||||
$query->where('name', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
})->orWhereHas('table', function ($query) use ($search) {
|
||||
$query->where('name', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Status filter
|
||||
if ($request->has('status') && !empty($request->status)) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$dateFrom = Carbon::parse($request->date_from)->startOfDay();
|
||||
$query->where('start_time', '>=', $dateFrom);
|
||||
}
|
||||
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$dateTo = Carbon::parse($request->date_to)->endOfDay();
|
||||
$query->where('start_time', '<=', $dateTo);
|
||||
}
|
||||
|
||||
// Sorting
|
||||
$sortColumn = $request->sort ?? 'start_time';
|
||||
$sortDirection = $request->direction ?? 'desc';
|
||||
|
||||
// Handle related column sorting
|
||||
if ($sortColumn === 'user') {
|
||||
$query->join('users', 'bookings.user_id', '=', 'users.id')
|
||||
->select('bookings.*')
|
||||
->orderBy('users.name', $sortDirection);
|
||||
} elseif ($sortColumn === 'table') {
|
||||
$query->join('tables', 'bookings.table_id', '=', 'tables.id')
|
||||
->select('bookings.*')
|
||||
->orderBy('tables.name', $sortDirection);
|
||||
} else {
|
||||
$query->orderBy($sortColumn, $sortDirection);
|
||||
}
|
||||
|
||||
$bookings = $query->paginate(10)->withQueryString();
|
||||
|
||||
return view('admin.bookings.index', compact('bookings'));
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$booking = Booking::with(['table', 'user'])->findOrFail($id);
|
||||
return view('admin.bookings.show', compact('booking'));
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$booking = Booking::findOrFail($id);
|
||||
$tables = Table::all();
|
||||
return view('admin.bookings.edit', compact('booking', 'tables'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'table_id' => 'required|exists:tables,id',
|
||||
'start_time' => 'required|date',
|
||||
'end_time' => 'required|date|after:start_time',
|
||||
]);
|
||||
|
||||
$booking = Booking::findOrFail($id);
|
||||
$booking->update($request->all());
|
||||
|
||||
return redirect()->route('admin.bookings.index')
|
||||
->with('success', 'Booking berhasil diperbarui');
|
||||
}
|
||||
|
||||
public function complete($id)
|
||||
{
|
||||
$booking = Booking::findOrFail($id);
|
||||
$booking->status = 'selesai';
|
||||
$booking->save();
|
||||
|
||||
return redirect()->route('admin.bookings.index')
|
||||
->with('success', 'Booking berhasil diselesaikan');
|
||||
}
|
||||
|
||||
public function cancel($id)
|
||||
{
|
||||
$booking = Booking::findOrFail($id);
|
||||
$booking->status = 'cancelled';
|
||||
$booking->save();
|
||||
|
||||
return redirect()->route('admin.bookings.index')
|
||||
->with('success', 'Booking berhasil dibatalkan');
|
||||
}
|
||||
|
||||
public function export(Request $request)
|
||||
{
|
||||
$filename = 'bookings-' . Carbon::now()->format('Y-m-d') . '.xlsx';
|
||||
return Excel::download(new BookingsExport($request), $filename);
|
||||
}
|
||||
}
|
|
@ -5,24 +5,110 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Table;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class TableController extends Controller
|
||||
{
|
||||
public function kelolaMeja()
|
||||
/**
|
||||
* Display a listing of the tables.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$tables = Table::where('venue_id', auth()->user()->venue_id)->paginate(10);
|
||||
$query = Table::query()->where('venue_id', auth()->user()->venue_id);
|
||||
|
||||
// Search by name
|
||||
if ($request->has('search') && $request->search != '') {
|
||||
$query->where('name', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
// Filter by status
|
||||
if ($request->has('status') && $request->status != '') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
$tables = $query->latest()->paginate(10);
|
||||
|
||||
return view('admin.tables.index', compact('tables'));
|
||||
}
|
||||
|
||||
public function editTable($id)
|
||||
/**
|
||||
* Show the form for creating a new table.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$table = Table::findOrFail($id);
|
||||
return view('admin.tables.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created table in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'brand' => 'required|string|max:255',
|
||||
'status' => 'required|in:Available,Booked,Unavailable',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
Table::create([
|
||||
'name' => $request->name,
|
||||
'brand' => $request->brand,
|
||||
'status' => $request->status,
|
||||
'venue_id' => auth()->user()->venue_id,
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.tables.index')
|
||||
->with('success', 'Meja baru berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified table.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$table = Table::where('venue_id', auth()->user()->venue_id)->findOrFail($id);
|
||||
return view('admin.tables.edit', compact('table'));
|
||||
}
|
||||
|
||||
public function updateTable(Request $request, $id)
|
||||
/**
|
||||
* Update the specified table in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$table = Table::findOrFail($id);
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'brand' => 'required|string|max:255',
|
||||
'status' => 'required|in:Available,Booked,Unavailable',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$table = Table::where('venue_id', auth()->user()->venue_id)->findOrFail($id);
|
||||
|
||||
$table->update([
|
||||
'name' => $request->name,
|
||||
|
@ -30,7 +116,22 @@ public function updateTable(Request $request, $id)
|
|||
'status' => $request->status,
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.tables.index')->with('success', 'Data meja berhasil diperbarui.');
|
||||
return redirect()->route('admin.tables.index')
|
||||
->with('success', 'Data meja berhasil diperbarui.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified table from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$table = Table::where('venue_id', auth()->user()->venue_id)->findOrFail($id);
|
||||
$table->delete();
|
||||
|
||||
return redirect()->route('admin.tables.index')
|
||||
->with('success', 'Meja berhasil dihapus.');
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
"laravel/sanctum": "^3.3",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/ui": "^4.6",
|
||||
"maatwebsite/excel": "^1.1",
|
||||
"midtrans/midtrans-php": "^2.6"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "edf30558ed2abcf5bae02cf31ec6ee71",
|
||||
"content-hash": "bce8ddce84cf8a2d572b421f6992903e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
|
@ -2290,6 +2290,65 @@
|
|||
],
|
||||
"time": "2024-09-21T08:32:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maatwebsite/excel",
|
||||
"version": "v1.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Maatwebsite/Laravel-Excel.git",
|
||||
"reference": "0c67aba8387726458d42461eae91a3415593bbc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/0c67aba8387726458d42461eae91a3415593bbc4",
|
||||
"reference": "0c67aba8387726458d42461eae91a3415593bbc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"phpoffice/phpexcel": "~1.8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~0.9",
|
||||
"orchestra/testbench": "~2.2.0@dev",
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Maatwebsite\\Excel\\": "src/"
|
||||
},
|
||||
"classmap": [
|
||||
"src/Maatwebsite/Excel",
|
||||
"tests/TestCase.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maatwebsite.nl",
|
||||
"email": "patrick@maatwebsite.nl"
|
||||
}
|
||||
],
|
||||
"description": "An eloquent way of importing and exporting Excel and CSV in Laravel 4 with the power of PHPExcel",
|
||||
"keywords": [
|
||||
"PHPExcel",
|
||||
"batch",
|
||||
"csv",
|
||||
"excel",
|
||||
"export",
|
||||
"import",
|
||||
"laravel"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Maatwebsite/Laravel-Excel/issues",
|
||||
"source": "https://github.com/Maatwebsite/Laravel-Excel/tree/master"
|
||||
},
|
||||
"time": "2014-07-10T09:06:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "midtrans/midtrans-php",
|
||||
"version": "2.6.2",
|
||||
|
@ -2851,6 +2910,68 @@
|
|||
],
|
||||
"time": "2023-02-08T01:06:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpexcel",
|
||||
"version": "1.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PHPExcel.git",
|
||||
"reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/372c7cbb695a6f6f1e62649381aeaa37e7e70b32",
|
||||
"reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-xml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"PHPExcel": "Classes/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maarten Balliauw",
|
||||
"homepage": "http://blog.maartenballiauw.be"
|
||||
},
|
||||
{
|
||||
"name": "Mark Baker"
|
||||
},
|
||||
{
|
||||
"name": "Franck Lefevre",
|
||||
"homepage": "http://blog.rootslabs.net"
|
||||
},
|
||||
{
|
||||
"name": "Erik Tilt"
|
||||
}
|
||||
],
|
||||
"description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||
"homepage": "http://phpexcel.codeplex.com",
|
||||
"keywords": [
|
||||
"OpenXML",
|
||||
"excel",
|
||||
"php",
|
||||
"spreadsheet",
|
||||
"xls",
|
||||
"xlsx"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPOffice/PHPExcel/issues",
|
||||
"source": "https://github.com/PHPOffice/PHPExcel/tree/master"
|
||||
},
|
||||
"abandoned": "phpoffice/phpspreadsheet",
|
||||
"time": "2015-05-01T07:00:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.3",
|
||||
|
|
|
@ -1,42 +1,298 @@
|
|||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Daftar Booking</h1>
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-col md:flex-row justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-4 md:mb-0">
|
||||
<i class="fas fa-calendar-check mr-2"></i>Daftar Booking
|
||||
</h1>
|
||||
|
||||
<div class="overflow-x-auto bg-white rounded shadow">
|
||||
<table class="min-w-full table-auto">
|
||||
<thead class="bg-gray-100">
|
||||
<div class="flex flex-col md:flex-row gap-3 w-full md:w-auto">
|
||||
<a href="{{ route('admin.dashboard') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left mr-1"></i> Kembali
|
||||
</a>
|
||||
<a href="{{ route('admin.bookings.export') }}" class="btn btn-success">
|
||||
<i class="fas fa-file-excel mr-1"></i> Export Excel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Card -->
|
||||
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
|
||||
<form action="{{ route('admin.bookings.index') }}" method="GET" class="space-y-4">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="w-full md:w-1/4">
|
||||
<label for="search" class="block text-sm font-medium text-gray-700 mb-1">Cari</label>
|
||||
<div class="relative">
|
||||
<input type="text" name="search" id="search" placeholder="Cari user atau meja..."
|
||||
class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-200"
|
||||
value="{{ request('search') }}">
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<i class="fas fa-search text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-1/4">
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 mb-1">Status</label>
|
||||
<select name="status" id="status" class="form-select w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-200">
|
||||
<option value="">Semua Status</option>
|
||||
<option value="booked" {{ request('status') == 'booked' ? 'selected' : '' }}>Booked</option>
|
||||
<option value="selesai" {{ request('status') == 'selesai' ? 'selected' : '' }}>Selesai</option>
|
||||
<option value="cancelled" {{ request('status') == 'cancelled' ? 'selected' : '' }}>Dibatalkan</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-1/4">
|
||||
<label for="date_from" class="block text-sm font-medium text-gray-700 mb-1">Dari Tanggal</label>
|
||||
<input type="date" name="date_from" id="date_from"
|
||||
class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-200"
|
||||
value="{{ request('date_from') }}">
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-1/4">
|
||||
<label for="date_to" class="block text-sm font-medium text-gray-700 mb-1">Sampai Tanggal</label>
|
||||
<input type="date" name="date_to" id="date_to"
|
||||
class="form-input w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-200"
|
||||
value="{{ request('date_to') }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<a href="{{ route('admin.bookings.index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-redo mr-1"></i> Reset
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-filter mr-1"></i> Filter
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Bookings Table Card -->
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="p-4 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
|
||||
<h2 class="font-semibold text-lg text-gray-700">Data Booking</h2>
|
||||
<div class="text-sm text-gray-500">
|
||||
Total: <span class="font-semibold">{{ $bookings->total() }}</span> data
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left">User</th>
|
||||
<th class="px-4 py-2 text-left">Meja</th>
|
||||
<th class="px-4 py-2 text-left">Mulai</th>
|
||||
<th class="px-4 py-2 text-left">Selesai</th>
|
||||
<th class="px-4 py-2 text-left">Status</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'user', 'direction' => request('direction') == 'asc' && request('sort') == 'user' ? 'desc' : 'asc'])) }}" class="flex items-center">
|
||||
User
|
||||
@if(request('sort') == 'user')
|
||||
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
|
||||
@else
|
||||
<i class="fas fa-sort ml-1 opacity-50"></i>
|
||||
@endif
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'table', 'direction' => request('direction') == 'asc' && request('sort') == 'table' ? 'desc' : 'asc'])) }}" class="flex items-center">
|
||||
Meja
|
||||
@if(request('sort') == 'table')
|
||||
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
|
||||
@else
|
||||
<i class="fas fa-sort ml-1 opacity-50"></i>
|
||||
@endif
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'start_time', 'direction' => request('direction') == 'asc' && request('sort') == 'start_time' ? 'desc' : 'asc'])) }}" class="flex items-center">
|
||||
Mulai
|
||||
@if(request('sort') == 'start_time')
|
||||
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
|
||||
@else
|
||||
<i class="fas fa-sort ml-1 opacity-50"></i>
|
||||
@endif
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'end_time', 'direction' => request('direction') == 'asc' && request('sort') == 'end_time' ? 'desc' : 'asc'])) }}" class="flex items-center">
|
||||
Selesai
|
||||
@if(request('sort') == 'end_time')
|
||||
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
|
||||
@else
|
||||
<i class="fas fa-sort ml-1 opacity-50"></i>
|
||||
@endif
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<a href="{{ route('admin.bookings.index', array_merge(request()->all(), ['sort' => 'status', 'direction' => request('direction') == 'asc' && request('sort') == 'status' ? 'desc' : 'asc'])) }}" class="flex items-center">
|
||||
Status
|
||||
@if(request('sort') == 'status')
|
||||
<i class="fas fa-sort-{{ request('direction') == 'asc' ? 'up' : 'down' }} ml-1"></i>
|
||||
@else
|
||||
<i class="fas fa-sort ml-1 opacity-50"></i>
|
||||
@endif
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Aksi
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse ($bookings as $booking)
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-2">{{ $booking->user->name }}</td>
|
||||
<td class="px-4 py-2">{{ $booking->table->name }}</td>
|
||||
<td class="px-4 py-2">{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i d/m') }}</td>
|
||||
<td class="px-4 py-2">{{ \Carbon\Carbon::parse($booking->end_time)->format('H:i d/m') }}</td>
|
||||
<td class="px-4 py-2">
|
||||
<span
|
||||
class="text-sm px-2 py-1 rounded
|
||||
{{ $booking->status === 'booked' ? 'bg-blue-200 text-blue-800' : ($booking->status === 'selesai' ? 'bg-green-200 text-green-800' : 'bg-red-200 text-red-800') }}">
|
||||
{{ ucfirst($booking->status) }}
|
||||
<tr class="hover:bg-gray-50 transition">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 h-10 w-10">
|
||||
<img class="h-10 w-10 rounded-full" src="https://ui-avatars.com/api/?name={{ urlencode($booking->user->name) }}&color=7F9CF5&background=EBF4FF" alt="">
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="text-sm font-medium text-gray-900">{{ $booking->user->name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ $booking->user->email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm font-medium text-gray-900">{{ $booking->table->name }}</div>
|
||||
<div class="text-sm text-gray-500">Kapasitas: {{ $booking->table->capacity }} orang</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm text-gray-900">{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i') }}</div>
|
||||
<div class="text-sm text-gray-500">{{ \Carbon\Carbon::parse($booking->start_time)->format('d M Y') }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm text-gray-900">{{ \Carbon\Carbon::parse($booking->end_time)->format('H:i') }}</div>
|
||||
<div class="text-sm text-gray-500">{{ \Carbon\Carbon::parse($booking->end_time)->format('d M Y') }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
@if($booking->status === 'booked')
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
<i class="fas fa-clock mr-1"></i> Booked
|
||||
</span>
|
||||
@elseif($booking->status === 'selesai')
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
<i class="fas fa-check mr-1"></i> Selesai
|
||||
</span>
|
||||
@else
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
||||
<i class="fas fa-times mr-1"></i> Dibatalkan
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<div class="flex space-x-2">
|
||||
<a href="{{ route('admin.bookings.show', $booking->id) }}" class="text-blue-600 hover:text-blue-900" title="Detail">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
|
||||
@if($booking->status === 'booked')
|
||||
<a href="{{ route('admin.bookings.edit', $booking->id) }}" class="text-yellow-600 hover:text-yellow-900" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
|
||||
<form action="{{ route('admin.bookings.complete', $booking->id) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button type="submit" class="text-green-600 hover:text-green-900" title="Selesai" onclick="return confirm('Apakah anda yakin menyelesaikan booking ini?')">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form action="{{ route('admin.bookings.cancel', $booking->id) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button type="submit" class="text-red-600 hover:text-red-900" title="Batalkan" onclick="return confirm('Apakah anda yakin membatalkan booking ini?')">
|
||||
<i class="fas fa-ban"></i>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-2 text-center text-gray-500">Belum ada data booking.</td>
|
||||
<td colspan="6" class="px-6 py-10 text-center text-gray-500">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<i class="fas fa-calendar-times text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="text-lg font-medium">Belum ada data booking</p>
|
||||
<p class="text-sm">Coba ubah filter atau tambahkan booking baru</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-3 bg-gray-50 border-t border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm text-gray-500">
|
||||
Menampilkan {{ $bookings->firstItem() ?? 0 }} - {{ $bookings->lastItem() ?? 0 }} dari {{ $bookings->total() }} data
|
||||
</div>
|
||||
<div>
|
||||
{{ $bookings->appends(request()->query())->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.btn {
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent rounded-md font-semibold text-xs uppercase tracking-wider transition focus:outline-none focus:ring-2 focus:ring-offset-2;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-gray-200 text-gray-700 hover:bg-gray-300 focus:ring-gray-500;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
@apply bg-green-600 text-white hover:bg-green-700 focus:ring-green-500;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
@apply flex rounded-md;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
@apply -ml-px;
|
||||
}
|
||||
|
||||
.page-link {
|
||||
@apply relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50;
|
||||
}
|
||||
|
||||
.page-item.active .page-link {
|
||||
@apply z-10 bg-blue-50 border-blue-500 text-blue-600;
|
||||
}
|
||||
|
||||
.page-item.disabled .page-link {
|
||||
@apply bg-gray-50 text-gray-500 cursor-not-allowed;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Date range validation
|
||||
const dateFrom = document.getElementById('date_from');
|
||||
const dateTo = document.getElementById('date_to');
|
||||
|
||||
if (dateFrom && dateTo) {
|
||||
dateFrom.addEventListener('change', function() {
|
||||
dateTo.min = dateFrom.value;
|
||||
});
|
||||
|
||||
dateTo.addEventListener('change', function() {
|
||||
dateFrom.max = dateTo.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
|
@ -1,57 +1,211 @@
|
|||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Admin {{ $venue->name }}</h1>
|
||||
<p>Selamat datang, {{ auth()->user()->name }}!</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 my-6">
|
||||
<a href="#" class="bg-blue-500 text-white p-4 rounded-lg">
|
||||
<p class="text-sm">Jumlah Booking Hari Ini</p>
|
||||
<p class="text-2xl font-bold">{{ $todayBookings }}</p>
|
||||
</a>
|
||||
<a href="#" class="bg-gray-600 text-white p-4 rounded-lg">
|
||||
<p class="text-sm">Total Meja</p>
|
||||
<p class="text-2xl font-bold">{{ $totalTables }}</p>
|
||||
</a>
|
||||
<a href="#" class="bg-red-600 text-white p-4 rounded-lg">
|
||||
<p class="text-sm">Meja Sedang Digunakan</p>
|
||||
<p class="text-2xl font-bold">{{ $usedTables }}</p>
|
||||
</a>
|
||||
<a href="#" class="bg-green-600 text-white p-4 rounded-lg">
|
||||
<p class="text-sm">Meja Tersedia</p>
|
||||
<p class="text-2xl font-bold">{{ $availableTables }}</p>
|
||||
</a>
|
||||
<div class="bg-gray-50 min-h-screen">
|
||||
<div class="p-6">
|
||||
<!-- Header and Welcome -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-800">Dashboard {{ $venue->name }}</h1>
|
||||
<p class="text-gray-600 mt-1">Selamat datang, {{ auth()->user()->name }}!</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm text-gray-500">{{ now()->format('l, d F Y') }}</p>
|
||||
<p class="text-2xl font-semibold text-gray-800">{{ now()->format('H:i') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="font-semibold text-lg mt-8 mb-2">Booking Terbaru</h2>
|
||||
<!-- Stats Cards - Row 1 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
||||
<!-- Today's Revenue -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border-l-4 border-green-500 hover:shadow-md transition">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Pendapatan Hari Ini</p>
|
||||
<p class="text-2xl font-bold text-gray-800">Rp{{ number_format($todayRevenue, 0, ',', '.') }}</p>
|
||||
</div>
|
||||
<div class="text-green-500 p-2 bg-green-50 rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">Pendapatan Bulan Ini: Rp{{ number_format($monthlyRevenue, 0, ',', '.') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Today's Bookings -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border-l-4 border-blue-500 hover:shadow-md transition">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Booking Hari Ini</p>
|
||||
<p class="text-2xl font-bold text-gray-800">{{ $todayBookings }}</p>
|
||||
</div>
|
||||
<div class="text-blue-500 p-2 bg-blue-50 rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" 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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-2 space-x-4">
|
||||
<p class="text-xs text-gray-500">Pending: <span class="font-semibold text-amber-500">{{ $pendingBookings }}</span></p>
|
||||
<p class="text-xs text-gray-500">Paid: <span class="font-semibold text-green-500">{{ $paidBookings }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Tables -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border-l-4 border-purple-500 hover:shadow-md transition">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Total Meja</p>
|
||||
<p class="text-2xl font-bold text-gray-800">{{ $totalTables }}</p>
|
||||
</div>
|
||||
<div class="text-purple-500 p-2 bg-purple-50 rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-2 space-x-4">
|
||||
<p class="text-xs text-gray-500">Tersedia: <span class="font-semibold text-green-500">{{ $availableTables }}</span></p>
|
||||
<p class="text-xs text-gray-500">Digunakan: <span class="font-semibold text-red-500">{{ $usedTables }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Usage -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border-l-4 border-amber-500 hover:shadow-md transition">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Penggunaan Meja</p>
|
||||
<p class="text-2xl font-bold text-gray-800">{{ $totalTables > 0 ? round(($usedTables / $totalTables) * 100) : 0 }}%</p>
|
||||
</div>
|
||||
<div class="text-amber-500 p-2 bg-amber-50 rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2.5 mt-3">
|
||||
<div class="bg-amber-500 h-2.5 rounded-full" style="width: {{ $totalTables > 0 ? ($usedTables / $totalTables) * 100 : 0 }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts and Recent Bookings -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Weekly Bookings Chart -->
|
||||
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm p-6">
|
||||
<h2 class="font-semibold text-lg mb-4">Booking Per Hari (7 Hari Terakhir)</h2>
|
||||
<div class="h-80" id="bookingsChart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Bookings -->
|
||||
<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">Booking Terbaru</h2>
|
||||
<a href="{{ route('admin.bookings.index') }}" class="text-sm text-blue-600 hover:underline">Lihat Semua</a>
|
||||
</div>
|
||||
|
||||
@if($recentBookings->isEmpty())
|
||||
<p class="text-gray-500">Belum ada booking terbaru.</p>
|
||||
<p class="text-gray-500 text-center py-4">Belum ada booking terbaru.</p>
|
||||
@else
|
||||
<div class="bg-white rounded shadow overflow-x-auto">
|
||||
<table class="w-full table-auto">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left">Nama User</th>
|
||||
<th class="px-4 py-2 text-left">Meja</th>
|
||||
<th class="px-4 py-2 text-left">Waktu</th>
|
||||
<th class="px-4 py-2 text-left">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<div class="space-y-4">
|
||||
@foreach($recentBookings as $booking)
|
||||
<tr class="border-t">
|
||||
<td class="px-4 py-2">{{ $booking->user->name }}</td>
|
||||
<td class="px-4 py-2">{{ $booking->table->name }}</td>
|
||||
<td class="px-4 py-2">{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i') }} -
|
||||
{{ \Carbon\Carbon::parse($booking->end_time)->format('H:i') }}</td>
|
||||
<td class="px-4 py-2 capitalize">{{ $booking->status }}</td>
|
||||
</tr>
|
||||
<div class="flex items-center p-3 border border-gray-100 rounded-lg hover:bg-gray-50 transition">
|
||||
<div class="p-2 mr-3 bg-gray-100 rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800">{{ $booking->user->name }}</p>
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<span class="mr-2">{{ $booking->table->name }}</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full {{
|
||||
$booking->status === 'paid' ? 'bg-green-100 text-green-800' :
|
||||
($booking->status === 'pending' ? 'bg-amber-100 text-amber-800' :
|
||||
'bg-gray-100 text-gray-800')
|
||||
}}">
|
||||
{{ ucfirst($booking->status) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right text-sm text-gray-500">
|
||||
<p>{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i') }}</p>
|
||||
<p>{{ \Carbon\Carbon::parse($booking->start_time)->format('d/m') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Booking Chart
|
||||
var bookingData = @json($lastWeekBookings);
|
||||
|
||||
var options = {
|
||||
series: [{
|
||||
name: 'Booking',
|
||||
data: bookingData.map(item => item.count)
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 300,
|
||||
toolbar: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
borderRadius: 4,
|
||||
columnWidth: '60%',
|
||||
}
|
||||
},
|
||||
colors: ['#3b82f6'],
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
xaxis: {
|
||||
categories: bookingData.map(item => item.date),
|
||||
axisBorder: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: 'Jumlah Booking'
|
||||
},
|
||||
labels: {
|
||||
formatter: function (val) {
|
||||
return Math.floor(val);
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val + " booking";
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: '#f3f4f6',
|
||||
strokeDashArray: 5
|
||||
}
|
||||
};
|
||||
|
||||
var chart = new ApexCharts(document.querySelector("#bookingsChart"), options);
|
||||
chart.render();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
|
@ -0,0 +1,70 @@
|
|||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="p-6 bg-gray-50">
|
||||
<div class="flex items-center mb-6">
|
||||
<a href="{{ route('admin.tables.index') }}" class="text-blue-600 hover:text-blue-800 mr-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" 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>
|
||||
</a>
|
||||
<h1 class="text-3xl font-bold text-gray-800">Tambah Meja Baru</h1>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="p-6">
|
||||
<form action="{{ route('admin.tables.store') }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Nama Meja</label>
|
||||
<input type="text" id="name" name="name" value="{{ old('name') }}"
|
||||
placeholder="Masukkan 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 @error('name') border-red-500 @enderror">
|
||||
@error('name')
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="brand" class="block text-sm font-medium text-gray-700 mb-1">Merek</label>
|
||||
<input type="text" id="brand" name="brand" value="{{ old('brand') }}"
|
||||
placeholder="Masukkan merek meja"
|
||||
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 @error('brand') border-red-500 @enderror">
|
||||
@error('brand')
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 mb-1">Status</label>
|
||||
<select id="status" 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 @error('status') border-red-500 @enderror">
|
||||
<option value="Available" {{ old('status') === 'Available' ? 'selected' : '' }}>Available</option>
|
||||
<option value="Booked" {{ old('status') === 'Booked' ? 'selected' : '' }}>Booked</option>
|
||||
<option value="Unavailable" {{ old('status') === 'Unavailable' ? 'selected' : '' }}>Unavailable
|
||||
</option>
|
||||
</select>
|
||||
@error('status')
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<a href="{{ route('admin.tables.index') }}"
|
||||
class="bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition-colors duration-300">
|
||||
Batal
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors duration-300">
|
||||
Simpan Meja
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -1,34 +1,70 @@
|
|||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Edit Meja: {{ $table->name }}</h1>
|
||||
<div class="p-6 bg-gray-50">
|
||||
<div class="flex items-center mb-6">
|
||||
<a href="{{ route('admin.tables.index') }}" class="text-blue-600 hover:text-blue-800 mr-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" 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>
|
||||
</a>
|
||||
<h1 class="text-3xl font-bold text-gray-800">Edit Meja: {{ $table->name }}</h1>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('admin.tables.update', $table->id) }}" method="POST" class="space-y-4">
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="p-6">
|
||||
<form action="{{ route('admin.tables.update', $table->id) }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Nama Meja</label>
|
||||
<input type="text" name="name" value="{{ $table->name }}" class="w-full border border-gray-300 p-2 rounded">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Nama Meja</label>
|
||||
<input type="text" id="name" name="name" value="{{ old('name', $table->name) }}"
|
||||
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 @error('name') border-red-500 @enderror">
|
||||
@error('name')
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Merek</label>
|
||||
<input type="text" name="brand" value="{{ $table->brand }}"
|
||||
class="w-full border border-gray-300 p-2 rounded">
|
||||
<label for="brand" class="block text-sm font-medium text-gray-700 mb-1">Merek</label>
|
||||
<input type="text" id="brand" name="brand" value="{{ old('brand', $table->brand) }}"
|
||||
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 @error('brand') border-red-500 @enderror">
|
||||
@error('brand')
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Status</label>
|
||||
<select name="status" class="w-full border border-gray-300 p-2 rounded">
|
||||
<option value="Available" {{ $table->status === 'Available' ? 'selected' : '' }}>Available</option>
|
||||
<option value="Booked" {{ $table->status === 'Booked' ? 'selected' : '' }}>Booked</option>
|
||||
<option value="Unavailable" {{ $table->status === 'Unavailable' ? 'selected' : '' }}>Unavailable</option>
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 mb-1">Status</label>
|
||||
<select id="status" 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 @error('status') border-red-500 @enderror">
|
||||
<option value="Available" {{ old('status', $table->status) === 'Available' ? 'selected' : '' }}>
|
||||
Available</option>
|
||||
<option value="Booked" {{ old('status', $table->status) === 'Booked' ? 'selected' : '' }}>Booked
|
||||
</option>
|
||||
<option value="Unavailable" {{ old('status', $table->status) === 'Unavailable' ? 'selected' : '' }}>Unavailable</option>
|
||||
</select>
|
||||
@error('status')
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded">Simpan</button>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<a href="{{ route('admin.tables.index') }}"
|
||||
class="bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition-colors duration-300">
|
||||
Batal
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors duration-300">
|
||||
Simpan Perubahan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -1,48 +1,125 @@
|
|||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Kelola Meja</h1>
|
||||
<div class="p-6 bg-gray-50">
|
||||
<!-- Header dengan Action Button -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-800">Kelola Meja</h1>
|
||||
<a href="{{ route('admin.tables.create') }}"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center transition-colors duration-300">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Tambah Meja Baru
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto bg-white shadow rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<!-- 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 -->
|
||||
<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-4 py-2 text-left font-semibold text-gray-600">Nama Meja</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-gray-600">Merek</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-gray-600">Status</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-gray-600">Aksi</th>
|
||||
<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="divide-y divide-gray-100">
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse ($tables as $table)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-2">{{ $table->name }}</td>
|
||||
<td class="px-4 py-2">{{ $table->brand }}</td>
|
||||
<td class="px-4 py-2">
|
||||
<span class="px-2 py-1 rounded-full text-xs font-medium
|
||||
<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 === 'Booked' ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800') }}">
|
||||
{{ $table->status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<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:underline">Edit</a>
|
||||
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-4 py-4 text-center text-gray-500">Belum ada data meja.</td>
|
||||
<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 class="mt-4">
|
||||
{{ $tables->links() }}
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6">
|
||||
{{ $tables->withQueryString()->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -7,77 +7,190 @@
|
|||
<title>Admin Panel - {{ config('app.name') }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
.nav-item {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.nav-item.active {
|
||||
position: relative;
|
||||
background-color: rgb(239, 246, 255);
|
||||
color: rgb(37, 99, 235);
|
||||
font-weight: 500;
|
||||
}
|
||||
.nav-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 4px;
|
||||
background-color: rgb(37, 99, 235);
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
.nav-item:hover:not(.active) {
|
||||
background-color: rgb(249, 250, 251);
|
||||
color: rgb(55, 65, 81);
|
||||
}
|
||||
.dropdown-transition {
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body x-data="{ sidebarOpen: true }" class="flex">
|
||||
<body x-data="{ sidebarOpen: true, userDropdownOpen: false }" class="bg-gray-50">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- Sidebar Overlay -->
|
||||
<div x-show="sidebarOpen" @click="sidebarOpen = false" class="fixed inset-0 z-20 bg-black bg-opacity-50 lg:hidden"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div :class="sidebarOpen ? 'w-64' : 'w-16'" class="bg-white border-r h-screen transition-all duration-300">
|
||||
<div class="flex justify-between items-center p-4">
|
||||
<div :class="sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0 lg:w-20'"
|
||||
class="fixed inset-y-0 left-0 z-30 w-64 bg-white shadow-lg transition-all duration-300 transform lg:relative lg:translate-x-0">
|
||||
|
||||
<!-- Sidebar Header -->
|
||||
<div class="flex items-center justify-between h-16 px-4 border-b">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="font-bold text-lg" x-show="sidebarOpen">Admin Panel</span>
|
||||
</div>
|
||||
<button @click="sidebarOpen = !sidebarOpen" class="text-gray-600 focus:outline-none">
|
||||
<svg x-show="sidebarOpen" 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="M6 18L18 6M6 6l12 12" />
|
||||
<div class="p-2 bg-blue-600 rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<svg x-show="!sidebarOpen" 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="M4 6h16M4 12h16M4 18h16" />
|
||||
</div>
|
||||
<span class="font-bold text-lg text-gray-800" x-show="sidebarOpen">VenueApp</span>
|
||||
</div>
|
||||
<button @click="sidebarOpen = !sidebarOpen" class="p-1 rounded-md hover:bg-gray-100 focus:outline-none">
|
||||
<svg x-show="sidebarOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
||||
</svg>
|
||||
<svg x-show="!sidebarOpen" class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex flex-col justify-between h-full mt-4">
|
||||
<ul>
|
||||
<li><a href="{{ route('admin.dashboard') }}" class="block px-4 py-2 hover:bg-gray-100">📊 <span
|
||||
x-show="sidebarOpen">Dashboard</span></a></li>
|
||||
<li><a href="{{ route('admin.tables.index') }}" class="block px-4 py-2 hover:bg-gray-100">🪑 <span
|
||||
x-show="sidebarOpen">Kelola
|
||||
Meja</span></a></li>
|
||||
<li><a href="{{ route('admin.bookings.index') }}" class="block px-4 py-2 hover:bg-gray-100">📅 <span
|
||||
x-show="sidebarOpen">Daftar
|
||||
Booking</span></a></li>
|
||||
<li><a href="#" class="block px-4 py-2 hover:bg-gray-100">👥 <span x-show="sidebarOpen">Data
|
||||
User</span></a></li>
|
||||
<li><a href="#" class="block px-4 py-2 hover:bg-gray-100">🔔 <span
|
||||
x-show="sidebarOpen">Notifikasi</span></a></li>
|
||||
<li><a href="#" class="block px-4 py-2 hover:bg-gray-100">⚙️ <span
|
||||
x-show="sidebarOpen">Pengaturan</span></a></li>
|
||||
<li>
|
||||
<div class="relative mt-4 px-4" x-data="{ open: false }">
|
||||
<button @click="open = !open" class="flex items-center w-full text-left">
|
||||
<span class="truncate" x-show="sidebarOpen">{{ auth()->user()->name }}</span>
|
||||
<svg x-show="sidebarOpen" class="ml-1 h-4 w-4" fill="none" stroke="currentColor"
|
||||
stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
|
||||
<div class="px-2 py-4">
|
||||
<div x-show="sidebarOpen" class="text-xs font-semibold text-gray-400 uppercase tracking-wider px-3 mb-2">
|
||||
Menu Utama
|
||||
</div>
|
||||
<nav class="space-y-1">
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="nav-item flex items-center px-3 py-2.5 rounded-lg {{ request()->routeIs('admin.dashboard') ? '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="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span x-show="sidebarOpen">Dashboard</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.tables.index') }}"
|
||||
class="nav-item flex items-center px-3 py-2.5 rounded-lg {{ request()->routeIs('admin.tables.*') ? '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="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>
|
||||
<span x-show="sidebarOpen">Kelola Meja</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.bookings.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">Daftar Booking</span>
|
||||
</a>
|
||||
|
||||
<a href="#"
|
||||
class="nav-item flex items-center px-3 py-2.5 rounded-lg">
|
||||
<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="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span x-show="sidebarOpen">Data User</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div x-show="sidebarOpen" class="text-xs font-semibold text-gray-400 uppercase tracking-wider px-3 mb-2 mt-6">
|
||||
Sistem
|
||||
</div>
|
||||
<nav class="space-y-1">
|
||||
<a href="#"
|
||||
class="nav-item flex items-center px-3 py-2.5 rounded-lg">
|
||||
<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="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span x-show="sidebarOpen">Notifikasi</span>
|
||||
<span class="ml-auto bg-red-500 text-white px-2 py-0.5 rounded-full text-xs" x-show="sidebarOpen">3</span>
|
||||
</a>
|
||||
|
||||
<a href="#"
|
||||
class="nav-item flex items-center px-3 py-2.5 rounded-lg">
|
||||
<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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span x-show="sidebarOpen">Pengaturan</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- User Profile -->
|
||||
<div class="absolute bottom-0 w-full border-t border-gray-200">
|
||||
<div x-data="{ open: false }" class="relative p-4">
|
||||
<button @click="open = !open" class="flex items-center w-full text-left focus:outline-none">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center text-white font-semibold">
|
||||
{{ substr(auth()->user()->name, 0, 1) }}
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="sidebarOpen" class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-800 truncate">{{ auth()->user()->name }}</p>
|
||||
<p class="text-xs text-gray-500 truncate">{{ auth()->user()->email }}</p>
|
||||
</div>
|
||||
<svg x-show="sidebarOpen" xmlns="http://www.w3.org/2000/svg" class="ml-auto h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div x-show="open" @click.outside="open = false"
|
||||
class="absolute left-4 mt-2 w-48 bg-white border rounded shadow-md z-10" x-cloak>
|
||||
<a href="#" class="block px-4 py-2 text-sm hover:bg-gray-100">Edit Profil</a>
|
||||
<!-- Dropdown -->
|
||||
<div x-show="open" @click.outside="open = false" class="absolute bottom-full left-0 mb-1 w-full bg-white rounded-lg shadow-lg border border-gray-200 py-1 dropdown-transition">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Profile</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Settings</a>
|
||||
<div class="border-t border-gray-200 my-1"></div>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-gray-100">Logout</button>
|
||||
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-gray-100">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- User Dropdown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main content -->
|
||||
<div class="flex-1 p-6 bg-gray-50 min-h-screen">
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- Top Header -->
|
||||
<header class="bg-white shadow-sm lg:hidden">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between">
|
||||
<button @click="sidebarOpen = !sidebarOpen" class="p-1 rounded-md text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 lg:hidden">
|
||||
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="lg:hidden">
|
||||
<span class="font-semibold text-lg">{{ config('app.name') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="flex-1 overflow-x-hidden overflow-y-auto">
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stack('scripts')
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -34,8 +34,18 @@
|
|||
Route::middleware(['auth', 'is_admin'])->prefix('admin')->group(function () {
|
||||
Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard');
|
||||
Route::get('/bookings', [BookingsController::class, 'index'])->name('admin.bookings.index');
|
||||
Route::get('/tables', [TableController::class, 'kelolaMeja'])->name('admin.tables.index');
|
||||
Route::get('/bookings/export', [BookingsController::class, 'export'])->name('admin.bookings.export');
|
||||
Route::get('/bookings/{id}', [BookingsController::class, 'show'])->name('admin.bookings.show');
|
||||
Route::get('/bookings/{id}/edit', [BookingsController::class, 'edit'])->name('admin.bookings.edit');
|
||||
Route::put('/bookings/{id}', [BookingsController::class, 'update'])->name('admin.bookings.update');
|
||||
Route::patch('/bookings/{id}/complete', [BookingsController::class, 'complete'])->name('admin.bookings.complete');
|
||||
Route::patch('/bookings/{id}/cancel', [BookingsController::class, 'cancel'])->name('admin.bookings.cancel');
|
||||
|
||||
Route::get('/tables/{id}/edit', [TableController::class, 'editTable'])->name('admin.tables.edit');
|
||||
Route::put('/tables/{id}', [TableController::class, 'updateTable'])->name('admin.tables.update');
|
||||
// CRUD routes untuk manajemen meja
|
||||
Route::get('/tables', [TableController::class, 'index'])->name('admin.tables.index');
|
||||
Route::get('/tables/create', [TableController::class, 'create'])->name('admin.tables.create');
|
||||
Route::post('/tables', [TableController::class, 'store'])->name('admin.tables.store');
|
||||
Route::get('/tables/{id}/edit', [TableController::class, 'edit'])->name('admin.tables.edit');
|
||||
Route::put('/tables/{id}', [TableController::class, 'update'])->name('admin.tables.update');
|
||||
Route::delete('/tables/{id}', [TableController::class, 'destroy'])->name('admin.tables.destroy');
|
||||
});
|
Loading…
Reference in New Issue