diff --git a/app/Http/Controllers/Admin/AdditionalController.php b/app/Http/Controllers/Admin/AdditionalController.php index 5313555..393863e 100644 --- a/app/Http/Controllers/Admin/AdditionalController.php +++ b/app/Http/Controllers/Admin/AdditionalController.php @@ -3,68 +3,24 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; -use Illuminate\Http\Request; use App\Models\Additional; -use Illuminate\Support\Facades\Validator; +use App\Http\Requests\Admin\AdditionalRequest; // Gunakan FormRequest baru class AdditionalController extends Controller { - // Store: Simpan Data Baru - public function store(Request $request) + public function store(AdditionalRequest $request) { - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:3|max:100', - 'harga' => 'required|numeric|min:0', - ], [ - 'required' => 'Kolom :attribute wajib diisi.', - 'numeric' => ':attribute harus berupa angka.', - 'string' => 'Input :attribute harus berupa teks valid.', - 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', - 'min' => ':attribute minimal harus :min karakter/nilai.', - 'numeric' => ':attribute harus berupa angka.', - ], [ - 'nama' => 'nama additional', - 'harga' => 'harga additional', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_modal', 'createAdd'); - } - Additional::create($request->only(['nama', 'harga'])); + Additional::create($request->validated()); return redirect()->back()->with('success', 'Additional berhasil ditambahkan!'); } - // Update: Simpan Perubahan - public function update(Request $request, string $id) + public function update(AdditionalRequest $request, string $id) { $additional = Additional::findOrFail($id); - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:3|max:100', - 'harga' => 'required|numeric|min:0', - ], [ - 'required' => 'Kolom :attribute wajib diisi.', - 'numeric' => ':attribute harus berupa angka.', - 'string' => 'Input :attribute harus berupa teks valid.', - 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', - 'min' => ':attribute minimal harus :min karakter/nilai.', - 'numeric' => ':attribute harus berupa angka.', - ], [ - 'nama' => 'nama additional', - 'harga' => 'harga additional', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_id_add', $id); - } - $additional->update($request->only(['nama', 'harga'])); + $additional->update($request->validated()); return redirect()->back()->with('success', 'Additional berhasil diperbarui!'); } - // Destroy: Hapus Data public function destroy(string $id) { $additional = Additional::findOrFail($id); diff --git a/app/Http/Controllers/Admin/BerandaController.php b/app/Http/Controllers/Admin/BerandaController.php index 5f9a2fb..6fa075c 100644 --- a/app/Http/Controllers/Admin/BerandaController.php +++ b/app/Http/Controllers/Admin/BerandaController.php @@ -7,55 +7,66 @@ use App\Models\BookingFoto; use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use App\Services\DashboardService; class BerandaController extends Controller { public function index() { - $now = Carbon::now(); - $lastMonth = Carbon::now()->subMonth(); $today = Carbon::today(); - $getGrowth = function ($current, $previous) { - if ($previous <= 0) return $current > 0 ? 100 : 0; - return round((($current - $previous) / $previous) * 100, 1); - }; - $currPendapatan = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_transaksi', 'diterima')->sum('total_bayar') + - BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_booking', 'diterima')->sum('total_bayar'); - - $currMasuk = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->count() + - BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->count(); - - $currSelesai = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_transaksi', 'selesai')->count() + - BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_booking', 'selesai')->count(); - - $currBatal = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_transaksi', 'ditolak')->count() + - BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_booking', 'ditolak')->count(); - $prevPendapatan = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_transaksi', 'diterima')->sum('total_bayar') + - BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_booking', 'diterima')->sum('total_bayar'); - - $prevMasuk = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->count() + - BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->count(); - - $prevSelesai = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_transaksi', 'selesai')->count() + - BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_booking', 'selesai')->count(); - - $prevBatal = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_transaksi', 'ditolak')->count() + - BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_booking', 'ditolak')->count(); - $stat = [ - 'pendapatan' => $currPendapatan, - 'pendapatan_grow' => $getGrowth($currPendapatan, $prevPendapatan), - 'masuk_count' => $currMasuk, - 'masuk_grow' => $getGrowth($currMasuk, $prevMasuk), - 'selesai_count' => $currSelesai, - 'selesai_grow' => $getGrowth($currSelesai, $prevSelesai), - 'batal_count' => $currBatal, - 'batal_grow' => $getGrowth($currBatal, $prevBatal), - ]; - $buketToday = TransaksiBuket::with(['pelanggan', 'buket'])->whereDate('tgl_ambil', $today)->where('status_transaksi', 'diterima')->get(); - $fotoToday = BookingFoto::with(['pelanggan', 'paketFoto'])->whereDate('tgl_booking', $today)->where('status_booking', 'diterima')->orderBy('jam_mulai', 'asc')->get(); - $pesananBuket = TransaksiBuket::with(['pelanggan', 'buket'])->where('status_transaksi', 'menunggu_verifikasi')->latest()->get(); - $pesananFoto = BookingFoto::with(['pelanggan', 'paketFoto'])->where('status_booking', 'menunggu_verifikasi')->latest()->get(); - + $stat = DashboardService::getStats(); + $buketToday = TransaksiBuket::with(['pelanggan', 'buket'])->whereDate('tgl_ambil', $today)->where('status_transaksi', 'diterima')->get(); + $fotoToday = BookingFoto::with(['pelanggan', 'paketFoto'])->whereDate('tgl_booking', $today)->where('status_booking', 'diterima')->orderBy('jam_mulai', 'asc')->get(); + $pesananBuket = TransaksiBuket::where('status_transaksi', 'menunggu_verifikasi')->latest()->get(); + $pesananFoto = BookingFoto::where('status_booking', 'menunggu_verifikasi')->latest()->get(); return view('admin.beranda.index', compact('stat', 'buketToday', 'fotoToday', 'pesananBuket', 'pesananFoto')); } + // public function index() + // { + // $now = Carbon::now(); + // $lastMonth = Carbon::now()->subMonth(); + // $today = Carbon::today(); + // $getGrowth = function ($current, $previous) { + // if ($previous <= 0) return $current > 0 ? 100 : 0; + // return round((($current - $previous) / $previous) * 100, 1); + // }; + // $currPendapatan = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_transaksi', 'diterima')->sum('total_bayar') + + // BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_booking', 'diterima')->sum('total_bayar'); + + // $currMasuk = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->count() + + // BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->count(); + + // $currSelesai = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_transaksi', 'selesai')->count() + + // BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_booking', 'selesai')->count(); + + // $currBatal = TransaksiBuket::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_transaksi', 'ditolak')->count() + + // BookingFoto::whereMonth('created_at', $now->month)->whereYear('created_at', $now->year)->where('status_booking', 'ditolak')->count(); + // $prevPendapatan = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_transaksi', 'diterima')->sum('total_bayar') + + // BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_booking', 'diterima')->sum('total_bayar'); + + // $prevMasuk = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->count() + + // BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->count(); + + // $prevSelesai = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_transaksi', 'selesai')->count() + + // BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_booking', 'selesai')->count(); + + // $prevBatal = TransaksiBuket::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_transaksi', 'ditolak')->count() + + // BookingFoto::whereMonth('created_at', $lastMonth->month)->whereYear('created_at', $lastMonth->year)->where('status_booking', 'ditolak')->count(); + // $stat = [ + // 'pendapatan' => $currPendapatan, + // 'pendapatan_grow' => $getGrowth($currPendapatan, $prevPendapatan), + // 'masuk_count' => $currMasuk, + // 'masuk_grow' => $getGrowth($currMasuk, $prevMasuk), + // 'selesai_count' => $currSelesai, + // 'selesai_grow' => $getGrowth($currSelesai, $prevSelesai), + // 'batal_count' => $currBatal, + // 'batal_grow' => $getGrowth($currBatal, $prevBatal), + // ]; + // $buketToday = TransaksiBuket::with(['pelanggan', 'buket'])->whereDate('tgl_ambil', $today)->where('status_transaksi', 'diterima')->get(); + // $fotoToday = BookingFoto::with(['pelanggan', 'paketFoto'])->whereDate('tgl_booking', $today)->where('status_booking', 'diterima')->orderBy('jam_mulai', 'asc')->get(); + // $pesananBuket = TransaksiBuket::with(['pelanggan', 'buket'])->where('status_transaksi', 'menunggu_verifikasi')->latest()->get(); + // $pesananFoto = BookingFoto::with(['pelanggan', 'paketFoto'])->where('status_booking', 'menunggu_verifikasi')->latest()->get(); + + // return view('admin.beranda.index', compact('stat', 'buketToday', 'fotoToday', 'pesananBuket', 'pesananFoto')); + // } } diff --git a/app/Http/Controllers/Admin/BuketController.php b/app/Http/Controllers/Admin/BuketController.php index ceb30b1..bbafe6b 100644 --- a/app/Http/Controllers/Admin/BuketController.php +++ b/app/Http/Controllers/Admin/BuketController.php @@ -4,8 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\Buket; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Validator; +use App\Http\Requests\Admin\BuketRequest; // Panggil Request baru kamu use Illuminate\Support\Facades\Storage; class BuketController extends Controller @@ -16,104 +15,29 @@ public function index() return view('admin.produk-buket.index', compact('buket')); } - public function store(Request $request) + public function store(BuketRequest $request) { - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:3|max:100', - 'ukuran' => 'required|in:S,M,L', - 'kategori' => 'required|in:single,fresh,premium_fresh,artificial', - 'harga' => 'required|numeric|min:0', - 'request_khusus' => 'nullable|string|max:255', - 'deskripsi' => 'required|string|min:10', - 'foto' => 'required|image|mimes:jpeg,png,jpg|max:2048', - ], [ - 'required' => 'Kolom :attribute tidak boleh kosong.', - 'string' => 'Input :attribute harus berupa teks valid.', - 'min' => ':attribute terlalu pendek, minimal harus :min karakter.', - 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', - 'in' => 'Pilihan :attribute tidak sesuai dengan data yang tersedia.', - 'numeric' => ':attribute harus berupa angka.', - 'image' => ':attribute harus berupa file gambar (foto).', - 'mimes' => 'Format :attribute tidak didukung. Gunakan format: jpeg, png, atau jpg.', - 'foto.max' => 'Ukuran :attribute terlalu besar, maksimal adalah 2MB.', - ], [ - 'nama' => 'nama buket', - 'ukuran' => 'ukuran buket', - 'kategori' => 'kategori buket', - 'harga' => 'harga', - 'request_khusus' => 'request khusus', - 'deskripsi' => 'deskripsi produk', - 'foto' => 'foto produk', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_modal', 'create'); - } - $path = null; + $data = $request->validated(); if ($request->hasFile('foto')) { $file = $request->file('foto'); $filename = time() . '_' . $file->getClientOriginalName(); - $path = $file->storeAs('img/buket', $filename, 'public'); + $data['foto'] = $file->storeAs('img/buket', $filename, 'public'); } - Buket::create([ - 'nama' => $request->nama, - 'ukuran' => $request->ukuran, - 'kategori' => $request->kategori, - 'harga' => $request->harga, - 'request_khusus' => $request->request_khusus, - 'deskripsi' => $request->deskripsi, - 'foto' => $path, - ]); + Buket::create($data); return redirect()->back()->with('success', 'Produk buket berhasil ditambahkan!'); } - public function update(Request $request, string $id) + public function update(BuketRequest $request, string $id) { $buket = Buket::findOrFail($id); - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:3|max:100', - 'ukuran' => 'required|in:S,M,L', - 'kategori' => 'required|in:single,fresh,premium_fresh,artificial', - 'harga' => 'required|numeric|min:0', - 'request_khusus' => 'nullable|string|max:255', - 'deskripsi' => 'required|string|min:10', - 'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', - ], [ - 'required' => 'Kolom :attribute tidak boleh kosong.', - 'string' => 'Input :attribute harus berupa teks valid.', - 'min' => ':attribute terlalu pendek, minimal harus :min karakter.', - 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', - 'in' => 'Pilihan :attribute tidak sesuai dengan data yang tersedia.', - 'numeric' => ':attribute harus berupa angka.', - 'image' => ':attribute harus berupa file gambar (foto).', - 'mimes' => 'Format :attribute tidak didukung. Gunakan format: jpeg, png, atau jpg.', - 'foto.max' => 'Ukuran :attribute terlalu besar, maksimal adalah 2MB.', - ], [ - 'nama' => 'nama buket', - 'ukuran' => 'ukuran buket', - 'kategori' => 'kategori buket', - 'harga' => 'harga', - 'request_khusus' => 'request khusus', - 'deskripsi' => 'deskripsi produk', - 'foto' => 'foto produk', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_id', $id); - } - $data = $request->only(['nama', 'ukuran', 'kategori', 'harga', 'request_khusus', 'deskripsi']); + $data = $request->validated(); if ($request->hasFile('foto')) { if ($buket->foto) { Storage::disk('public')->delete($buket->foto); } $file = $request->file('foto'); $filename = time() . '_' . $file->getClientOriginalName(); - $path = $file->storeAs('img/buket', $filename, 'public'); - $data['foto'] = $path; + $data['foto'] = $file->storeAs('img/buket', $filename, 'public'); } $buket->update($data); return redirect()->back()->with('success', 'Produk buket berhasil diperbarui!'); @@ -122,12 +46,10 @@ public function update(Request $request, string $id) public function destroy(string $id) { $buket = Buket::findOrFail($id); - if ($buket->foto) { Storage::disk('public')->delete($buket->foto); } $buket->delete(); - return redirect()->back()->with('success', 'Produk dan foto berhasil dihapus permanen!'); } } diff --git a/app/Http/Controllers/Admin/FotoController.php b/app/Http/Controllers/Admin/FotoController.php index 648f987..c3c95dd 100644 --- a/app/Http/Controllers/Admin/FotoController.php +++ b/app/Http/Controllers/Admin/FotoController.php @@ -5,15 +5,11 @@ use App\Http\Controllers\Controller; use App\Models\Additional; use App\Models\PaketFoto; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Validator; +use App\Http\Requests\Admin\FotoRequest; // Gunakan Request baru use Illuminate\Support\Facades\Storage; class FotoController extends Controller { - /** - * Display a listing of the resource. - */ public function index() { $foto = PaketFoto::latest()->get(); @@ -21,100 +17,30 @@ public function index() return view('admin.paket-foto.index', compact('foto', 'additional')); } - public function store(Request $request) + public function store(FotoRequest $request) { - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:3|max:100', - 'harga' => 'required|numeric|min:0', - 'durasi' => 'required|integer|min:0', - 'deskripsi' => 'required|string|min:10', - 'foto' => 'required|image|mimes:jpeg,png,jpg|max:2048', - ], [ - 'required' => 'Kolom :attribute wajib diisi.', - 'string' => 'Input :attribute harus berupa teks valid.', - 'integer' => 'Input :attribute harus berupa angka valid.', - 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', - 'image' => ':attribute harus berupa file gambar (foto).', - 'mimes' => 'Format :attribute tidak didukung. Gunakan format: jpeg, png, atau jpg.', - 'foto.max' => 'Ukuran :attribute maksimal 2MB.', - 'min' => ':attribute minimal harus :min karakter/nilai.', - 'numeric' => ':attribute harus berupa angka.', - - ], [ - 'nama' => 'nama paket', - 'harga' => 'harga paket', - 'durasi' => 'durasi paket', - 'foto' => 'foto paket', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_modal', 'createFoto'); + $data = $request->validated(); + if ($request->hasFile('foto')) { + $file = $request->file('foto'); + $filename = time() . '_' . $file->getClientOriginalName(); + $path = $file->storeAs('img/foto', $filename, 'public'); + $data['foto'] = $path; } - if (!$request->hasFile('foto')) { - return redirect()->back() - ->with('error', 'File foto tidak ditemukan!') - ->withInput(); - } - $file = $request->file('foto'); - $filename = time() . '_' . $file->getClientOriginalName(); - $path = $file->storeAs('img/foto', $filename, 'public'); - if (!$path) { - return redirect()->back() - ->with('error', 'Gagal upload file!') - ->withInput(); - } - PaketFoto::create([ - 'nama' => $request->nama, - 'harga' => $request->harga, - 'durasi' => $request->durasi, - 'deskripsi' => $request->deskripsi, - 'foto' => $path, - ]); + PaketFoto::create($data); return redirect()->back()->with('success', 'Paket foto baru berhasil ditambahkan!'); } - public function update(Request $request, string $id) + public function update(FotoRequest $request, string $id) { $paket = PaketFoto::findOrFail($id); - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:3|max:100', - 'harga' => 'required|numeric|min:0', - 'durasi' => 'required|integer|min:0', - 'deskripsi' => 'required|string|min:10', - 'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', - ], [ - 'required' => 'Kolom :attribute wajib diisi.', - 'string' => 'Input :attribute harus berupa teks valid.', - 'integer' => 'Input :attribute harus berupa angka valid.', - 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', - 'image' => ':attribute harus berupa file gambar (foto).', - 'mimes' => 'Format :attribute tidak didukung. Gunakan format: jpeg, png, atau jpg.', - 'foto.max' => 'Ukuran :attribute maksimal 2MB.', - 'min' => ':attribute minimal harus :min karakter/nilai.', - 'numeric' => ':attribute harus berupa angka.', - ], [ - 'nama' => 'nama paket', - 'harga' => 'harga paket', - 'durasi' => 'durasi paket', - 'foto' => 'foto paket', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_id_foto', $id); - } - $data = $request->only(['nama', 'harga', 'durasi', 'deskripsi']); + $data = $request->validated(); if ($request->hasFile('foto')) { if ($paket->foto) { Storage::disk('public')->delete($paket->foto); } $file = $request->file('foto'); $filename = time() . '_' . $file->getClientOriginalName(); - $path = $file->storeAs('img/foto', $filename, 'public'); - $data['foto'] = $path; + $data['foto'] = $file->storeAs('img/foto', $filename, 'public'); } $paket->update($data); return redirect()->back()->with('success', 'Paket foto berhasil diperbarui!'); @@ -122,14 +48,11 @@ public function update(Request $request, string $id) public function destroy(string $id) { - // Cari data berdasarkan primary key id_paket - $paket = PaketFoto::where('id_paket', $id)->firstOrFail(); - + $paket = PaketFoto::findOrFail($id); if ($paket->foto) { Storage::disk('public')->delete($paket->foto); } $paket->delete(); - return redirect()->back()->with('success', 'Paket foto dan filenya berhasil dihapus!'); } } diff --git a/app/Http/Controllers/Admin/ManajemenAdminController.php b/app/Http/Controllers/Admin/ManajemenAdminController.php index ea9ff26..eb46fb6 100644 --- a/app/Http/Controllers/Admin/ManajemenAdminController.php +++ b/app/Http/Controllers/Admin/ManajemenAdminController.php @@ -4,14 +4,11 @@ use App\Http\Controllers\Controller; use App\Models\User; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Validator; // <--- WAJIB ADA INI -use Illuminate\Routing\Controllers\HasMiddleware; -use Illuminate\Routing\Controllers\Middleware; +use App\Http\Requests\Admin\AdminRequest; // Panggil Request Baru +use Illuminate\Support\Facades\{Auth, Hash}; +use Illuminate\Routing\Controllers\{HasMiddleware, Middleware}; -class ManajemenAdminController extends Controller +class ManajemenAdminController extends Controller implements HasMiddleware { public static function middleware(): array { @@ -24,112 +21,38 @@ public static function middleware(): array }), ]; } + public function index() { - $admin = User::where('role', '!=', 'pemilik') - ->latest() - ->get(); + $admin = User::where('role', '!=', 'pemilik')->latest()->get(); return view('admin.kelola-admin.index', compact('admin')); } - public function store(Request $request) + public function store(AdminRequest $request) { - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:5|max:100', - 'username' => 'required|string|alpha_dash|max:50|unique:users,username', - 'email' => 'required|email:dns|max:255|unique:users,email,', - 'no_wa' => 'required|numeric|digits_between:10,15', - 'role' => 'required|in:admin_foto,admin_buket', - 'alamat' => 'required|string|max:255', - ], [ - 'required' => 'Kolom :attribute wajib diisi.', - 'unique' => ':attribute sudah terdaftar di sistem, gunakan yang lain.', - 'min' => ':attribute minimal harus berisi :min karakter.', - 'max' => ':attribute maksimal hanya boleh :max karakter.', - 'numeric' => ':attribute harus berupa angka.', - 'digits_between' => ':attribute harus berjumlah antara :min sampai :max digit.', - 'email' => 'Format :attribute tidak valid.', - 'alpha_dash' => ':attribute hanya boleh berisi huruf, angka, serta simbol - dan _', - 'in' => ':attribute yang dipilih tidak sesuai dengan pilihan yang tersedia.', - ], [ - 'nama' => 'nama lengkap', - 'username' => 'username', - 'email' => 'alamat email', - 'no_wa' => 'nomor WA', - 'role' => 'peran admin', - 'alamat' => 'alamat lengkap', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_modal', 'create'); - } - User::create([ - 'nama' => $request->nama, - 'username' => $request->username, - 'email' => $request->email, - 'no_wa' => $request->no_wa, - 'role' => $request->role, - 'alamat' => $request->alamat, - 'password' => Hash::make($request->username), - ]); + $data = $request->validated(); + $data['password'] = Hash::make($request->username); + User::create($data); return redirect()->back()->with('success', 'Admin berhasil ditambahkan!'); } - // --- Bagian UPDATE --- - public function update(Request $request, string $id) + public function update(AdminRequest $request, string $id) { $admin = User::findOrFail($id); - $validator = Validator::make($request->all(), [ - 'nama' => 'required|string|min:5|max:100', - 'username' => 'required|string|alpha_dash|max:50|unique:users,username,' . $id . ',id_user', - 'email' => 'required|email:dns|max:255|unique:users,email,' . $id . ',id_user', - 'no_wa' => 'required|numeric|digits_between:10,15', - 'role' => 'required|in:admin_foto,admin_buket', - 'alamat' => 'required|string|max:255', - ], [ - 'required' => 'Kolom :attribute wajib diisi.', - 'unique' => ':attribute sudah terdaftar di sistem, gunakan yang lain.', - 'min' => ':attribute minimal harus berisi :min karakter.', - 'max' => ':attribute maksimal hanya boleh :max karakter.', - 'numeric' => ':attribute harus berupa angka.', - 'digits_between' => ':attribute harus berjumlah antara :min sampai :max digit.', - 'email' => 'Format :attribute tidak valid.', - 'alpha_dash' => ':attribute hanya boleh berisi huruf, angka, serta simbol - dan _', - 'in' => ':attribute yang dipilih tidak sesuai dengan pilihan yang tersedia.', - ], [ - 'nama' => 'nama lengkap', - 'username' => 'username', - 'email' => 'alamat email', - 'no_wa' => 'nomor WA', - 'role' => 'peran admin', - 'alamat' => 'alamat lengkap', - ]); - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput() - ->with('error_id', $id); - } - $admin->update($request->only(['nama', 'username', 'email', 'no_wa', 'role', 'alamat'])); + $admin->update($request->validated()); return redirect()->back()->with('success', 'Data berhasil diperbarui!'); } public function destroy(string $id) { - // 1. Cari data admin berdasarkan ID $admin = User::findOrFail($id); - // 2. Keamanan tambahan: Jangan biarkan admin menghapus dirinya sendiri (opsional) if (Auth::id() == $admin->id_user) { return redirect()->back()->with('error', 'Anda tidak bisa menghapus akun sendiri!'); } - // 3. Eksekusi hapus $admin->delete(); - // 4. Kembali dengan pesan sukses return redirect()->route('admin.kelola-admin.index') ->with('success', 'Admin berhasil dihapus secara permanen.'); } diff --git a/app/Http/Controllers/Admin/PesananBuketController.php b/app/Http/Controllers/Admin/PesananBuketController.php index f936950..111b3bc 100644 --- a/app/Http/Controllers/Admin/PesananBuketController.php +++ b/app/Http/Controllers/Admin/PesananBuketController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\TransaksiBuket; +use App\Services\WhatsAppService; // Import Service use Illuminate\Http\Request; class PesananBuketController extends Controller @@ -19,61 +20,21 @@ public function index() public function updateStatus(Request $request, $id) { try { - $pesanan = \App\Models\TransaksiBuket::with(['pelanggan', 'buket'])->findOrFail($id); - if ($request->jenis === 'terima') { - $status = 'diterima'; - session()->flash('success', "Pesanan {$pesanan->no_invoice} telah diterima!"); - } elseif ($request->jenis === 'selesai') { - $status = 'selesai'; - session()->flash('success', "Pesanan {$pesanan->no_invoice} berhasil diselesaikan!"); - } else { - $status = 'ditolak'; - session()->flash('error', "Pesanan {$pesanan->no_invoice} telah ditolak!"); - } + $pesanan = TransaksiBuket::with(['pelanggan', 'buket'])->findOrFail($id); + $status = $request->jenis === 'terima' ? 'diterima' : ($request->jenis === 'selesai' ? 'selesai' : 'ditolak'); + $labels = [ + 'diterima' => 'telah diterima', + 'selesai' => 'berhasil diselesaikan', + 'ditolak' => 'telah ditolak' + ]; + $sessionKey = $status === 'ditolak' ? 'error' : 'success'; + $sessionMsg = "Pesanan {$pesanan->no_invoice} " . $labels[$status] . "!"; + session()->flash($sessionKey, $sessionMsg); $pesanan->update(['status_transaksi' => $status]); - $nama = $pesanan->pelanggan->nama; - $produk = $pesanan->buket->nama; - $tgl_obj = \Carbon\Carbon::parse($pesanan->tgl_ambil)->locale('id'); - $tanggal = $tgl_obj->translatedFormat('l, d F Y'); - $waktu = $tgl_obj->format('H:i'); - $total = number_format($pesanan->total_bayar, 0, ',', '.'); - $req = $pesanan->request ?? '-'; - $ucapan = $pesanan->ucapan ?? '-'; - $invoice = $pesanan->no_invoice; - $msg = null; - if ($status === 'diterima') { - $msg = "Halo Kak *{$nama}*,\n\n" . - "Pesanan Anda dengan Nomor Invoice: *{$invoice}* telah kami *TERIMA* dan masuk dalam daftar proses pengerjaan.\n\n" . - "*Rincian Pesanan:*\n" . - "- *Produk:* {$produk}\n" . - "- *Total Bayar:* Rp {$total}\n" . - "- *Request:* {$req}\n" . - "- *Ucapan:* \"{$ucapan}\"\n" . - "- *Waktu Pengambilan:* {$tanggal} pukul {$waktu} WIB\n\n" . - "Mohon simpan rincian ini dan ditunggu info selanjutnya ya Kak. Terima kasih!"; - } elseif ($status === 'ditolak') { - $msg = "Halo Kak *{$nama}*,\n\n" . - "Mengenai pesanan Anda dengan Nomor Invoice: *{$invoice}* terpaksa kami *TOLAK* dikarenakan:\n\n" . - "*[TULIS ALASAN DI SINI]*\n\n" . - "*Rincian Pesanan:*\n" . - "- *Produk:* {$produk}\n" . - "- *Total Bayar:* Rp {$total}\n" . - "- *Waktu Pengambilan:* {$tanggal}\n\n" . - "Admin kami akan segera menghubungi Kakak terkait proses pengembalian dana. Mohon maaf atas ketidaknyamanannya."; - } - $wa_url = null; - if ($msg) { - $no_wa = preg_replace('/[^0-9]/', '', $pesanan->pelanggan->no_wa); - if (str_starts_with($no_wa, '0')) { - $no_wa = '62' . substr($no_wa, 1); - } elseif (str_starts_with($no_wa, '8')) { - $no_wa = '62' . $no_wa; - } - $wa_url = "https://wa.me/{$no_wa}?text=" . urlencode($msg); - } + $waUrl = WhatsAppService::getBuketNotification($pesanan, $status); return response()->json([ 'success' => true, - 'wa_url' => $wa_url + 'wa_url' => $waUrl ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()], 500); diff --git a/app/Http/Controllers/Admin/PesananFotoController.php b/app/Http/Controllers/Admin/PesananFotoController.php index 295d425..ff7bacc 100644 --- a/app/Http/Controllers/Admin/PesananFotoController.php +++ b/app/Http/Controllers/Admin/PesananFotoController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\BookingFoto; +use App\Services\WhatsAppService; // Import Service use Illuminate\Http\Request; class PesananFotoController extends Controller @@ -24,72 +25,21 @@ public function index() public function updateStatus(Request $request, $id) { try { - $pesanan = \App\Models\BookingFoto::with(['pelanggan', 'paketFoto', 'detailAdditional.additional'])->findOrFail($id); - if ($request->jenis === 'terima') { - $status = 'diterima'; - session()->flash('success', "Booking {$pesanan->no_invoice} telah diterima!"); - } elseif ($request->jenis === 'selesai') { - $status = 'selesai'; - session()->flash('success', "Booking {$pesanan->no_invoice} berhasil diselesaikan!"); - } else { - $status = 'ditolak'; - session()->flash('error', "Booking {$pesanan->no_invoice} telah ditolak!"); - } + $pesanan = BookingFoto::with(['pelanggan', 'paketFoto', 'detailAdditional.additional'])->findOrFail($id); + $status = $request->jenis === 'terima' ? 'diterima' : ($request->jenis === 'selesai' ? 'selesai' : 'ditolak'); + $labels = [ + 'diterima' => 'telah diterima', + 'selesai' => 'berhasil diselesaikan', + 'ditolak' => 'telah ditolak' + ]; + $sessionKey = $status === 'ditolak' ? 'error' : 'success'; + $sessionMsg = "Booking {$pesanan->no_invoice} " . $labels[$status] . "!"; + session()->flash($sessionKey, $sessionMsg); $pesanan->update(['status_booking' => $status]); - $nama = $pesanan->pelanggan->nama; - $paket = $pesanan->paketFoto->nama; - $tgl_obj = \Carbon\Carbon::parse($pesanan->tgl_booking)->locale('id'); - $tanggal = $tgl_obj->translatedFormat('l, d F Y'); - $jam_mulai = \Carbon\Carbon::parse($pesanan->jam_mulai)->format('H:i'); - $jam_selesai = \Carbon\Carbon::parse($pesanan->jam_selesai)->format('H:i'); - $total = number_format($pesanan->total_bayar, 0, ',', '.'); - $invoice = $pesanan->no_invoice; - $list_additional = ""; - if ($pesanan->detailAdditional->count() > 0) { - foreach ($pesanan->detailAdditional as $item) { - $list_additional .= "- " . $item->additional->nama . " (x" . $item->qty . ")\n"; - } - } else { - $list_additional = "- Tidak ada additional\n"; - } - $msg = null; - if ($status === 'diterima') { - $msg = "Halo Kak *{$nama}* ,\n\n" . - "Booking foto Anda dengan Invoice: *{$invoice}* telah kami *TERIMA*. \n\n" . - "*Rincian Booking:*\n" . - "- *Paket:* {$paket}\n" . - "*Additional:*\n{$list_additional}" . - "- *Total Bayar:* Rp {$total}\n\n" . - "*Jadwal Sesi Foto:*\n" . - " Tanggal: {$tanggal}\n" . - " Jam: {$jam_mulai} - {$jam_selesai} WIB\n\n" . - "Mohon datang 15 menit sebelum jadwal dimulai ya Kak. Sampai jumpa di studio! "; - } elseif ($status === 'ditolak') { - $msg = "Halo Kak *{$nama}*,\n\n" . - "Mohon maaf, booking foto Anda dengan Invoice *{$invoice}* terpaksa kami *TOLAK* dikarenakan:\n\n" . - "*[TULIS ALASAN DI SINI]*\n\n" . - "*Rincian Booking:*\n" . - "- *Paket:* {$paket}\n" . - "*Additional:*\n{$list_additional}" . - "- *Total Bayar:* Rp {$total}\n" . - "*Jadwal Sesi Foto:*\n" . - " Tanggal: {$tanggal}\n" . - " Jam: {$jam_mulai} - {$jam_selesai} WIB\n\n" . - "Admin kami akan segera menghubungi Kakak untuk info pengembalian dana atau penjadwalan ulang. Terima kasih. "; - } - $wa_url = null; - if ($msg) { - $no_wa = preg_replace('/[^0-9]/', '', $pesanan->pelanggan->no_wa); - if (str_starts_with($no_wa, '0')) { - $no_wa = '62' . substr($no_wa, 1); - } elseif (str_starts_with($no_wa, '8')) { - $no_wa = '62' . $no_wa; - } - $wa_url = "https://wa.me/{$no_wa}?text=" . urlencode($msg); - } + $waUrl = WhatsAppService::getFotoNotification($pesanan, $status); return response()->json([ 'success' => true, - 'wa_url' => $wa_url + 'wa_url' => $waUrl ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()], 500); diff --git a/app/Http/Controllers/User/BookingFotoController.php b/app/Http/Controllers/User/BookingFotoController.php index 0cf4b4f..93ecddc 100755 --- a/app/Http/Controllers/User/BookingFotoController.php +++ b/app/Http/Controllers/User/BookingFotoController.php @@ -3,16 +3,13 @@ namespace App\Http\Controllers\User; use App\Http\Controllers\Controller; -use App\Models\Additional; -use App\Models\BookingFoto; -use App\Models\DetailAdditional; -use App\Models\PaketFoto; -use App\Models\Pelanggan; +use App\Models\{Additional, BookingFoto, DetailAdditional, PaketFoto, Pelanggan}; +use App\Http\Requests\User\BookingFotoRequest; +use App\Services\WhatsAppService; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; -use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Facades\Validator; +use Carbon\Carbon; class BookingFotoController extends Controller @@ -64,204 +61,87 @@ public function cekSlot(Request $request) } public function formulir(Request $request) { - $foto = PaketFoto::findOrFail($request->id_paket); - $durasiDasar = $foto->durasi; - $addonsDetails = []; - $totalAddon = 0; - $tambahanMenit = 0; - if ($request->has('addons')) { - foreach ($request->addons as $id => $qty) { - if ($qty > 0) { - $add = Additional::find($id); - if ($add) { - $subtotal = $add->harga * $qty; - $totalAddon += $subtotal; - - if ($id == 4) { - $tambahanMenit += (5 * $qty); - } elseif ($id == 6) { - $tambahanMenit += (10 * $qty); - } - - $addonsDetails[] = [ - 'nama' => $add->nama, - 'qty' => $qty, - 'subtotal' => $subtotal, - 'id' => $id - ]; - } - } - } - } - $totalDurasi = $durasiDasar + $tambahanMenit; - $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai); - $jamSelesaiBaru = $jamMulai->copy()->addMinutes($totalDurasi)->format('H:i'); - $grandTotal = $foto->harga + $totalAddon; + $details = $this->calculateDetails($request); if (!session()->has('payment_deadline')) { - $deadline = now()->addHours(2); - session()->put('payment_deadline', $deadline); - } else { - $deadline = session('payment_deadline'); + session()->put('payment_deadline', now()->addHours(2)); } - $sisaWaktu = now()->diffInSeconds($deadline, false); + $sisaWaktu = now()->diffInSeconds(session('payment_deadline'), false); if ($sisaWaktu <= 0) { session()->forget(['payment_deadline', 'addons']); return redirect()->route('booking.foto')->with('error', 'Waktu pembayaran telah habis.'); } - return view('user.pembayaran-foto', compact( - 'foto', - 'request', - 'addonsDetails', - 'grandTotal', - 'sisaWaktu', - 'jamSelesaiBaru' - )); + return view('user.pembayaran-foto', array_merge($details, [ + 'sisaWaktu' => $sisaWaktu, + 'request' => $request + ])); } public function cancelBooking() { session()->forget(['payment_deadline', 'addons']); return redirect()->route('booking.foto'); } - public function store(Request $request) + public function store(BookingFotoRequest $request) { - $validator = Validator::make($request->all(), [ - 'id_paket' => 'required|exists:paket_fotos,id_paket', - 'tgl_booking' => 'required|date|after_or_equal:today', - 'jam_mulai' => 'required', - 'nama' => 'required|string|min:3|max:100', - 'no_wa' => 'required|numeric|digits_between:10,15', - 'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048', - ], [ - 'required' => 'Kolom :attribute wajib diisi.', - 'string' => 'Input :attribute harus berupa teks valid.', - 'min' => ':attribute terlalu pendek, minimal :min karakter.', - 'max' => ':attribute terlalu panjang, maksimal :max karakter.', - 'numeric' => ':attribute harus berupa angka.', - 'digits_between' => ':attribute harus antara :min sampai :max digit.', - 'date' => 'Format tanggal pada :attribute tidak valid.', - 'after_or_equal' => ':attribute tidak boleh tanggal yang sudah lewat.', - 'image' => ':attribute harus berupa file gambar.', - 'mimes' => 'Format :attribute harus jpeg, png, atau jpg.', - 'bukti_bayar.max' => 'Ukuran :attribute maksimal adalah 2MB.', - ], [ - 'nama' => 'nama pemesan', - 'no_wa' => 'nomor WhatsApp', - 'bukti_bayar' => 'bukti pembayaran', - ]); - - DB::beginTransaction(); - try { - $paket = PaketFoto::findOrFail($request->id_paket); - $durasiMenit = $paket->durasi; - $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai); - $jamSelesai = $jamMulai->copy()->addMinutes($durasiMenit); - - $isTaken = BookingFoto::where('tgl_booking', $request->tgl_booking) - ->where('jam_mulai', $request->jam_mulai) - ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima', 'selesai']) - ->exists(); - - if ($isTaken) { - return back()->with('error', 'Mohon maaf, slot waktu ini baru saja diambil orang lain.'); + $details = $this->calculateDetails($request); + return DB::transaction(function () use ($request, $details) { + if (BookingFoto::where(['tgl_booking' => $request->tgl_booking, 'jam_mulai' => $request->jam_mulai]) + ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima'])->exists() + ) { + return back()->with('error', 'Slot baru saja diambil orang lain.'); } - - $pelanggan = Pelanggan::firstOrCreate( - ['no_wa' => $request->no_wa], - ['nama' => $request->nama] - ); - $grandTotal = $paket->harga; - $listAdditional = []; - $totalDurasi = $durasiMenit; - if ($request->has('addons')) { - foreach ($request->addons as $idAddon => $qty) { - if ($qty > 0) { - $add = \App\Models\Additional::find($idAddon); - if ($add) { - $subtotal = $add->harga * $qty; - $grandTotal += $subtotal; - - if ($idAddon == 4) $totalDurasi += (5 * $qty); - if ($idAddon == 6) $totalDurasi += (10 * $qty); - - $listAdditional[] = [ - 'id_additional' => $idAddon, - 'qty' => $qty, - 'subtotal' => $subtotal, - 'nama_barang' => $add->nama - ]; - } - } - } - } - $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai); - $jamSelesai = $jamMulai->copy()->addMinutes($totalDurasi); - $isTaken = \App\Models\BookingFoto::where('tgl_booking', $request->tgl_booking) - ->where('jam_mulai', $request->jam_mulai) - ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima']) - ->exists(); - if ($isTaken) { - return back()->with('error', 'Mohon maaf, slot waktu ini baru saja diambil orang lain.'); - } - $pelanggan = \App\Models\Pelanggan::create([ - 'nama' => $request->nama, - 'no_wa' => $request->no_wa - ]); - $pathBukti = null; - if ($request->hasFile('bukti_bayar')) { - $file = $request->file('bukti_bayar'); - $namaFile = 'bukti_' . time() . '.' . $file->getClientOriginalExtension(); - $pathBukti = $file->storeAs('img/payment/foto', $namaFile, 'public'); - } - $booking = \App\Models\BookingFoto::create([ - 'no_invoice' => 'INV-FOTO-' . strtoupper(\Illuminate\Support\Str::random(6)), - 'id_pelanggan' => $pelanggan->id_pelanggan, - 'id_paket' => $paket->id_paket, - 'tgl_booking' => $request->tgl_booking, - 'jam_mulai' => $request->jam_mulai, - 'jam_selesai' => $jamSelesai->format('H:i'), - 'total_bayar' => $grandTotal, - 'bukti_bayar' => $pathBukti, + $pelanggan = Pelanggan::firstOrCreate(['no_wa' => $request->no_wa], ['nama' => $request->nama]); + $pathBukti = $request->hasFile('bukti_bayar') + ? $request->file('bukti_bayar')->store('img/payment/foto', 'public') + : null; + $booking = BookingFoto::create([ + 'no_invoice' => 'INV-FOTO-' . strtoupper(Str::random(6)), + 'id_pelanggan' => $pelanggan->id_pelanggan, + 'id_paket' => $details['foto']->id_paket, + 'tgl_booking' => $request->tgl_booking, + 'jam_mulai' => $request->jam_mulai, + 'jam_selesai' => $details['jamSelesaiBaru'], + 'total_bayar' => $details['grandTotal'], + 'bukti_bayar' => $pathBukti, 'status_booking' => 'menunggu_verifikasi' ]); - foreach ($listAdditional as $item) { - \App\Models\DetailAdditional::create([ - 'id_booking' => $booking->id_booking, - 'id_additional' => $item['id_additional'], - 'qty' => $item['qty'], - 'subtotal' => $item['subtotal'] + foreach ($details['addonsDetails'] as $item) { + DetailAdditional::create([ + 'id_booking' => $booking->id_booking, + 'id_additional' => $item['id'], + 'qty' => $item['qty'], + 'subtotal' => $item['subtotal'] ]); } - \Illuminate\Support\Facades\DB::commit(); - $txtAddons = ""; - if (count($listAdditional) > 0) { - $txtAddons = "*Additional:*"; - foreach ($listAdditional as $item) { - $txtAddons .= "\n- " . $item['nama_barang'] . " (" . $item['qty'] . "x)"; - } - $txtAddons .= "\n"; - } - $pesan = "Halo Admin Flo.do! Saya sudah melakukan pembayaran untuk invoice {$booking->no_invoice}:\n\n" . - "*Data Pemesan:*\n" . - "Nama: {$request->nama}\n" . - "WA: {$request->no_wa}\n\n" . - "*Detail Booking:*\n" . - "Nama Paket: {$paket->nama}\n" . - $txtAddons . - "Tanggal: " . \Carbon\Carbon::parse($request->tgl_booking)->translatedFormat('l, d F Y') . "\n" . - "Jam: {$request->jam_mulai} - {$jamSelesai->format('H:i')} WIB\n" . - "Total: Rp " . number_format($grandTotal, 0, ',', '.') . "\n\n" . - "Mohon segera diproses, ya! Terima kasih."; - - $urlWA = "https://wa.me/6282337687878?text=" . urlencode($pesan); + $urlWA = WhatsAppService::getBookingFotoMessage($booking, $details['foto'], $details['addonsDetails'], $details['grandTotal'], $details['jamSelesaiBaru']); session()->forget('payment_deadline'); - return redirect()->route('booking.foto')->with([ - 'success' => 'Pesanan Berhasil Dibuat!', - 'waUrl' => $urlWA - ]); - } catch (\Exception $e) { - \Illuminate\Support\Facades\DB::rollBack(); - return back()->with('error', 'Error: ' . $e->getMessage())->withInput(); + return redirect()->route('booking.foto')->with(['success' => 'Pesanan Berhasil!', 'waUrl' => $urlWA]); + }); + } + private function calculateDetails($request) + { + $foto = PaketFoto::findOrFail($request->id_paket); + $totalAddon = 0; + $tambahanMenit = 0; + $addonsDetails = []; + + if ($request->has('addons')) { + foreach ($request->addons as $id => $qty) { + if ($qty > 0 && $add = Additional::find($id)) { + $subtotal = $add->harga * $qty; + $totalAddon += $subtotal; + if ($id == 4) $tambahanMenit += (5 * $qty); + if ($id == 6) $tambahanMenit += (10 * $qty); + $addonsDetails[] = ['nama' => $add->nama, 'qty' => $qty, 'subtotal' => $subtotal, 'id' => $id]; + } + } } + return [ + 'foto' => $foto, + 'addonsDetails' => $addonsDetails, + 'grandTotal' => $foto->harga + $totalAddon, + 'jamSelesaiBaru' => Carbon::createFromFormat('H:i', $request->jam_mulai) + ->addMinutes($foto->durasi + $tambahanMenit)->format('H:i') + ]; } } diff --git a/app/Http/Controllers/User/PesanBuketController.php b/app/Http/Controllers/User/PesanBuketController.php index 8637ef3..2142c2e 100755 --- a/app/Http/Controllers/User/PesanBuketController.php +++ b/app/Http/Controllers/User/PesanBuketController.php @@ -10,6 +10,8 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; +use App\Http\Requests\User\PesanBuketRequest; +use App\Services\WhatsAppService; class PesanBuketController extends Controller { diff --git a/app/Http/Requests/Admin/AdditionalRequest.php b/app/Http/Requests/Admin/AdditionalRequest.php new file mode 100644 index 0000000..e0ec682 --- /dev/null +++ b/app/Http/Requests/Admin/AdditionalRequest.php @@ -0,0 +1,60 @@ + 'required|string|min:3|max:100', + 'harga' => 'required|numeric|min:0', + ]; + } + + public function messages(): array + { + return [ + 'required' => 'Kolom :attribute wajib diisi.', + 'numeric' => ':attribute harus berupa angka.', + 'string' => 'Input :attribute harus berupa teks valid.', + 'min' => ':attribute minimal harus :min karakter/nilai.', + 'max' => ':attribute melebihi batas maksimal :max karakter.', + ]; + } + + public function attributes(): array + { + return [ + 'nama' => 'nama additional', + 'harga' => 'harga additional', + ]; + } + + /** + * Mengatur respon jika validasi gagal agar modal tidak tertutup. + */ + protected function failedValidation(Validator $validator) + { + $redirect = redirect()->back()->withErrors($validator)->withInput(); + + if ($this->isMethod('post')) { + // Untuk modal tambah additional + $redirect->with('error_modal', 'createAdd'); + } else { + // Untuk modal edit berdasarkan ID additional + $redirect->with('error_id_add', $this->route('additional')); + } + + throw new HttpResponseException($redirect); + } +} diff --git a/app/Http/Requests/Admin/AdminRequest.php b/app/Http/Requests/Admin/AdminRequest.php new file mode 100644 index 0000000..184619b --- /dev/null +++ b/app/Http/Requests/Admin/AdminRequest.php @@ -0,0 +1,74 @@ +route('id'); + + return [ + 'nama' => 'required|string|min:5|max:100', + 'username' => 'required|string|alpha_dash|max:50|unique:users,username,' . $adminId . ',id_user', + 'email' => 'required|email:dns|max:255|unique:users,email,' . $adminId . ',id_user', + 'no_wa' => 'required|numeric|digits_between:10,15', + 'role' => 'required|in:admin_foto,admin_buket', + 'alamat' => 'required|string|max:255', + ]; + } + + public function messages(): array + { + return [ + 'required' => 'Kolom :attribute wajib diisi.', + 'unique' => ':attribute sudah terdaftar di sistem, gunakan yang lain.', + 'min' => ':attribute minimal harus berisi :min karakter.', + 'max' => ':attribute maksimal hanya boleh :max karakter.', + 'numeric' => ':attribute harus berupa angka.', + 'digits_between' => ':attribute harus berjumlah antara :min sampai :max digit.', + 'email' => 'Format :attribute tidak valid.', + 'alpha_dash' => ':attribute hanya boleh berisi huruf, angka, serta simbol - dan _', + 'in' => ':attribute yang dipilih tidak sesuai pilihan yang tersedia.', + ]; + } + + public function attributes(): array + { + return [ + 'nama' => 'nama lengkap', + 'username' => 'username', + 'email' => 'alamat email', + 'no_wa' => 'nomor WA', + 'role' => 'peran admin', + 'alamat' => 'alamat lengkap', + ]; + } + + /** + * Penanganan khusus agar Modal tetap terbuka saat validasi gagal. + */ + protected function failedValidation(Validator $validator) + { + $redirect = redirect()->back()->withErrors($validator)->withInput(); + + if ($this->isMethod('post')) { + $redirect->with('error_modal', 'create'); + } else { + $redirect->with('error_id', $this->route('id')); + } + + throw new HttpResponseException($redirect); + } +} diff --git a/app/Http/Requests/Admin/BuketRequest.php b/app/Http/Requests/Admin/BuketRequest.php new file mode 100644 index 0000000..326c3c0 --- /dev/null +++ b/app/Http/Requests/Admin/BuketRequest.php @@ -0,0 +1,77 @@ + 'required|string|min:3|max:100', + 'ukuran' => 'required|in:S,M,L', + 'kategori' => 'required|in:single,fresh,premium_fresh,artificial', + 'harga' => 'required|numeric|min:0', + 'request_khusus' => 'nullable|string|max:255', + 'deskripsi' => 'required|string|min:10', + // Dinamis: required jika store (POST), nullable jika update (PUT) + 'foto' => $this->isMethod('post') + ? 'required|image|mimes:jpeg,png,jpg|max:2048' + : 'nullable|image|mimes:jpeg,png,jpg|max:2048', + ]; + } + + public function messages(): array + { + return [ + 'required' => 'Kolom :attribute tidak boleh kosong.', + 'string' => 'Input :attribute harus berupa teks valid.', + 'min' => ':attribute terlalu pendek, minimal harus :min karakter.', + 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', + 'in' => 'Pilihan :attribute tidak sesuai dengan data yang tersedia.', + 'numeric' => ':attribute harus berupa angka.', + 'image' => ':attribute harus berupa file gambar (foto).', + 'mimes' => 'Format :attribute tidak didukung. Gunakan format: jpeg, png, atau jpg.', + 'foto.max' => 'Ukuran :attribute terlalu besar, maksimal adalah 2MB.', + ]; + } + + public function attributes(): array + { + return [ + 'nama' => 'nama buket', + 'ukuran' => 'ukuran buket', + 'kategori' => 'kategori buket', + 'harga' => 'harga', + 'request_khusus' => 'request khusus', + 'deskripsi' => 'deskripsi produk', + 'foto' => 'foto produk', + ]; + } + + /** + * Trik untuk Modal: Mengembalikan error ke modal yang tepat + */ + protected function failedValidation(Validator $validator) + { + $redirect = redirect()->back()->withErrors($validator)->withInput(); + + if ($this->isMethod('post')) { + $redirect->with('error_modal', 'create'); + } else { + // Mengambil ID dari parameter route untuk modal edit + $redirect->with('error_id', $this->route('buket')); + } + + throw new HttpResponseException($redirect); + } +} diff --git a/app/Http/Requests/Admin/FotoRequest.php b/app/Http/Requests/Admin/FotoRequest.php new file mode 100644 index 0000000..afd4d3a --- /dev/null +++ b/app/Http/Requests/Admin/FotoRequest.php @@ -0,0 +1,72 @@ + 'required|string|min:3|max:100', + 'harga' => 'required|numeric|min:0', + 'durasi' => 'required|integer|min:0', + 'deskripsi' => 'required|string|min:10', + // Foto wajib saat Tambah (POST), opsional saat Edit (PUT) + 'foto' => $this->isMethod('post') + ? 'required|image|mimes:jpeg,png,jpg|max:2048' + : 'nullable|image|mimes:jpeg,png,jpg|max:2048', + ]; + } + + public function messages(): array + { + return [ + 'required' => 'Kolom :attribute wajib diisi.', + 'string' => 'Input :attribute harus berupa teks valid.', + 'integer' => 'Input :attribute harus berupa angka valid.', + 'numeric' => ':attribute harus berupa angka.', + 'min' => ':attribute minimal harus :min karakter/nilai.', + 'max' => ':attribute melebihi batas, maksimal :max karakter/KB.', + 'image' => ':attribute harus berupa file gambar (foto).', + 'mimes' => 'Format :attribute tidak didukung (jpeg, png, jpg).', + 'foto.max' => 'Ukuran :attribute maksimal 2MB.', + ]; + } + + public function attributes(): array + { + return [ + 'nama' => 'nama paket', + 'harga' => 'harga paket', + 'durasi' => 'durasi paket', + 'foto' => 'foto paket', + ]; + } + + /** + * Penanganan khusus untuk Modal di Admin + */ + protected function failedValidation(Validator $validator) + { + $redirect = redirect()->back()->withErrors($validator)->withInput(); + + if ($this->isMethod('post')) { + // Trigger modal tambah foto + $redirect->with('error_modal', 'createFoto'); + } else { + // Trigger modal edit berdasarkan ID paket + $redirect->with('error_id_foto', $this->route('foto')); + } + + throw new HttpResponseException($redirect); + } +} diff --git a/app/Http/Requests/Admin/ProfilRequest.php b/app/Http/Requests/Admin/ProfilRequest.php new file mode 100644 index 0000000..fd9f023 --- /dev/null +++ b/app/Http/Requests/Admin/ProfilRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/User/BookingFotoRequest.php b/app/Http/Requests/User/BookingFotoRequest.php new file mode 100644 index 0000000..701ed04 --- /dev/null +++ b/app/Http/Requests/User/BookingFotoRequest.php @@ -0,0 +1,51 @@ + 'required|exists:paket_fotos,id_paket', + 'tgl_booking' => 'required|date|after_or_equal:today', + 'jam_mulai' => 'required', + 'nama' => 'required|string|min:3|max:100', + 'no_wa' => 'required|numeric|digits_between:10,15', + 'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048', + ]; + } + + public function messages(): array + { + return [ + 'required' => 'Kolom :attribute wajib diisi.', + 'image' => ':attribute harus berupa file gambar.', + 'max' => 'Ukuran :attribute maksimal adalah 2MB.', + 'string' => 'Input :attribute harus berupa teks valid.', + 'min' => ':attribute terlalu pendek, minimal :min karakter.', + 'numeric' => ':attribute harus berupa angka.', + 'digits_between' => ':attribute harus antara :min sampai :max digit.', + 'date' => 'Format tanggal pada :attribute tidak valid.', + 'after_or_equal' => ':attribute tidak boleh tanggal yang sudah lewat.', + 'mimes' => 'Format :attribute harus jpeg, png, atau jpg.', + 'bukti_bayar.max' => 'Ukuran :attribute maksimal adalah 2MB.', + + ]; + } + public function attributes(): array + { + return [ + 'nama' => 'nama pemesan', + 'no_wa' => 'nomor WhatsApp', + 'bukti_bayar' => 'bukti pembayaran', + ]; + } +} diff --git a/app/Http/Requests/User/PesanBuketRequest.php b/app/Http/Requests/User/PesanBuketRequest.php new file mode 100644 index 0000000..b4f88a6 --- /dev/null +++ b/app/Http/Requests/User/PesanBuketRequest.php @@ -0,0 +1,63 @@ + 'required|exists:bukets,id_buket', // Tambahan validasi keamanan + 'nama' => 'required|string|min:3|max:100', + 'no_wa' => 'required|numeric|digits_between:10,15', + 'tgl_ambil' => 'required|date|after_or_equal:today', + 'waktu_ambil' => [ + 'required', + 'date_format:H:i', + 'after_or_equal:09:00', + 'before_or_equal:21:00', + ], + 'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048', + 'request_khusus' => 'nullable|string|max:255', + 'ucapan' => 'nullable|string|max:500', + ]; + } + + public function messages(): array + { + return [ + 'required' => 'Kolom :attribute wajib diisi.', + 'string' => 'Input :attribute harus berupa teks valid.', + 'min' => ':attribute terlalu pendek, minimal :min karakter.', + 'max' => ':attribute terlalu panjang, maksimal :max karakter.', + 'numeric' => ':attribute harus berupa angka.', + 'digits_between' => ':attribute harus antara :min sampai :max digit.', + 'date' => 'Format tanggal pada :attribute tidak valid.', + 'after_or_equal' => ':attribute tidak boleh tanggal yang sudah lewat.', + 'image' => ':attribute harus berupa file gambar.', + 'mimes' => 'Format :attribute harus jpeg, png, atau jpg.', + 'bukti_bayar.max' => 'Ukuran :attribute maksimal adalah 2MB.', + 'waktu_ambil.after_or_equal' => 'Jam operasional kami mulai pukul 09:00.', + 'waktu_ambil.before_or_equal' => 'Jam operasional kami berakhir pukul 21:00.', + ]; + } + + public function attributes(): array + { + return [ + 'nama' => 'nama pemesan', + 'no_wa' => 'nomor WhatsApp', + 'tgl_ambil' => 'tanggal pengambilan', + 'waktu_ambil' => 'waktu pengambilan', + 'bukti_bayar' => 'bukti pembayaran', + 'ucapan' => 'kartu ucapan', + ]; + } +} diff --git a/app/Services/DashboardService.php b/app/Services/DashboardService.php new file mode 100644 index 0000000..919efc6 --- /dev/null +++ b/app/Services/DashboardService.php @@ -0,0 +1,53 @@ +subMonth(); + + $curr = self::calculate($now); + $past = self::calculate($prev); + + return [ + 'pendapatan' => $curr['income'], + 'pendapatan_grow' => self::growth($curr['income'], $past['income']), + 'masuk_count' => $curr['total'], + 'masuk_grow' => self::growth($curr['total'], $past['total']), + 'selesai_count' => $curr['done'], + 'selesai_grow' => self::growth($curr['done'], $past['done']), + 'batal_count' => $curr['cancel'], + 'batal_grow' => self::growth($curr['cancel'], $past['cancel']), + ]; + } + + private static function calculate($date) + { + $m = $date->month; + $y = $date->year; + + // Gabungan query Buket & Foto + return [ + 'income' => TransaksiBuket::whereMonth('created_at', $m)->whereYear('created_at', $y)->where('status_transaksi', 'diterima')->sum('total_bayar') + + BookingFoto::whereMonth('created_at', $m)->whereYear('created_at', $y)->where('status_booking', 'diterima')->sum('total_bayar'), + 'total' => TransaksiBuket::whereMonth('created_at', $m)->whereYear('created_at', $y)->count() + + BookingFoto::whereMonth('created_at', $m)->whereYear('created_at', $y)->count(), + 'done' => TransaksiBuket::whereMonth('created_at', $m)->whereYear('created_at', $y)->where('status_transaksi', 'selesai')->count() + + BookingFoto::whereMonth('created_at', $m)->whereYear('created_at', $y)->where('status_booking', 'selesai')->count(), + 'cancel' => TransaksiBuket::whereMonth('created_at', $m)->whereYear('created_at', $y)->where('status_transaksi', 'ditolak')->count() + + BookingFoto::whereMonth('created_at', $m)->whereYear('created_at', $y)->where('status_booking', 'ditolak')->count(), + ]; + } + + private static function growth($current, $previous) + { + if ($previous <= 0) return $current > 0 ? 100 : 0; + return round((($current - $previous) / $previous) * 100, 1); + } +} diff --git a/app/Services/WhatsAppService.php b/app/Services/WhatsAppService.php new file mode 100644 index 0000000..9093405 --- /dev/null +++ b/app/Services/WhatsAppService.php @@ -0,0 +1,153 @@ +pelanggan->nama; + $produk = $pesanan->buket->nama; + $invoice = $pesanan->no_invoice; + $total = number_format($pesanan->total_bayar, 0, ',', '.'); + $req = $pesanan->request ?? '-'; + $ucapan = $pesanan->ucapan ?? '-'; + $tgl_obj = Carbon::parse($pesanan->tgl_ambil)->locale('id'); + $tanggal = $tgl_obj->translatedFormat('l, d F Y'); + $waktu = $tgl_obj->format('H:i'); + $msg = self::getBuketTemplate($status, $nama, $invoice, $produk, $total, $req, $ucapan, $tanggal, $waktu); + $no_wa = self::formatPhoneNumber($pesanan->pelanggan->no_wa); + + return "https://wa.me/{$no_wa}?text=" . urlencode($msg); + } + + private static function getBuketTemplate($status, $nama, $invoice, $produk, $total, $req, $ucapan, $tanggal, $waktu) + { + if ($status === 'diterima') { + return "Halo Kak *{$nama}*,\n\n" . + "Pesanan Anda dengan Nomor Invoice: *{$invoice}* telah kami *TERIMA* dan masuk dalam daftar proses pengerjaan.\n\n" . + "*Rincian Pesanan:*\n" . + "- *Produk:* {$produk}\n" . + "- *Total Bayar:* Rp {$total}\n" . + "- *Request:* {$req}\n" . + "- *Ucapan:* \"{$ucapan}\"\n" . + "- *Waktu Pengambilan:* {$tanggal} pukul {$waktu} WIB\n\n" . + "Mohon simpan rincian ini dan ditunggu info selanjutnya ya Kak. Terima kasih!"; + } + + return "Halo Kak *{$nama}*,\n\n" . + "Mengenai pesanan Anda dengan Nomor Invoice: *{$invoice}* terpaksa kami *TOLAK* dikarenakan:\n\n" . + "*[TULIS ALASAN DI SINI]*\n\n" . + "*Rincian Pesanan:*\n" . + "- *Produk:* {$produk}\n" . + "- *Total Bayar:* Rp {$total}\n" . + "- *Waktu Pengambilan:* {$tanggal}\n\n" . + "Admin kami akan segera menghubungi Kakak terkait proses pengembalian dana. Mohon maaf atas ketidaknyamanannya."; + } + + private static function formatPhoneNumber($number) + { + $number = preg_replace('/[^0-9]/', '', $number); + if (str_starts_with($number, '0')) { + return '62' . substr($number, 1); + } elseif (str_starts_with($number, '8')) { + return '62' . $number; + } + return $number; + } + public static function getFotoNotification($pesanan, $status) + { + if ($status === 'selesai') return null; + + $nama = $pesanan->pelanggan->nama; + $paket = $pesanan->paketFoto->nama; + $invoice = $pesanan->no_invoice; + $total = number_format($pesanan->total_bayar, 0, ',', '.'); + + // Format Tanggal & Jam + $tgl_obj = Carbon::parse($pesanan->tgl_booking)->locale('id'); + $tanggal = $tgl_obj->translatedFormat('l, d F Y'); + $jam = Carbon::parse($pesanan->jam_mulai)->format('H:i') . ' - ' . Carbon::parse($pesanan->jam_selesai)->format('H:i'); + + // Logika List Additional + $list_additional = $pesanan->detailAdditional->count() > 0 + ? $pesanan->detailAdditional->map(fn($item) => "- " . $item->additional->nama . " (x" . $item->qty . ")")->implode("\n") + : "- Tidak ada additional"; + + // Pilih Template + $msg = self::getFotoTemplate($status, $nama, $invoice, $paket, $list_additional, $total, $tanggal, $jam); + + // Format Nomor HP + $no_wa = self::formatPhoneNumber($pesanan->pelanggan->no_wa); + + return "https://wa.me/{$no_wa}?text=" . urlencode($msg); + } + + private static function getFotoTemplate($status, $nama, $invoice, $paket, $additional, $total, $tanggal, $jam) + { + if ($status === 'diterima') { + return "Halo Kak *{$nama}*,\n\n" . + "Booking foto Anda dengan Invoice: *{$invoice}* telah kami *TERIMA*. \n\n" . + "*Rincian Booking:*\n" . + "- *Paket:* {$paket}\n" . + "*Additional:*\n{$additional}\n" . + "- *Total Bayar:* Rp {$total}\n\n" . + "*Jadwal Sesi Foto:*\n" . + " Tanggal: {$tanggal}\n" . + " Jam: {$jam} WIB\n\n" . + "Mohon datang 15 menit sebelum jadwal dimulai ya Kak. Sampai jumpa di studio!"; + } + + return "Halo Kak *{$nama}*,\n\n" . + "Mohon maaf, booking foto Anda dengan Invoice *{$invoice}* terpaksa kami *TOLAK* dikarenakan:\n\n" . + "*[TULIS ALASAN DI SINI]*\n\n" . + "*Rincian Booking:*\n" . + "- *Paket:* {$paket}\n" . + "*Additional:*\n{$additional}\n" . + "- *Total Bayar:* Rp {$total}\n" . + "*Jadwal Sesi Foto:*\n" . + " Tanggal: {$tanggal}\n" . + " Jam: {$jam} WIB\n\n" . + "Admin kami akan segera menghubungi Kakak untuk info pengembalian dana atau penjadwalan ulang. Terima kasih."; + } + + public static function getPesananBuketMessage($transaksi, $pelanggan) + { + $total = number_format($transaksi->buket->harga, 0, ',', '.'); + + $pesan = "Halo Admin! Saya sudah melakukan pembayaran untuk invoice *{$transaksi->no_invoice}*:\n\n" . + "*Data Pemesan:*\n" . + "- Nama: {$pelanggan->nama}\n" . + "- WA: {$pelanggan->no_wa}\n\n" . + "*Detail Produk:*\n" . + "- Produk: {$transaksi->buket->nama}\n" . + "- Total: Rp {$total}\n\n" . + "Mohon segera diproses, ya! Terima kasih."; + + return "https://wa.me/6282337687878?text=" . urlencode($pesan); + } + public static function getBookingFotoMessage($booking, $paket, $addons, $grandTotal, $jamSelesai) + { + $txtAddons = ""; + if (count($addons) > 0) { + $txtAddons = "\n*Additional:*"; + foreach ($addons as $item) { + $txtAddons .= "\n- " . $item['nama'] . " (" . $item['qty'] . "x)"; + } + } + + $pesan = "Halo Admin Flo.do! Saya sudah melakukan pembayaran untuk invoice *{$booking->no_invoice}*:\n\n" . + "*Data Pemesan:*\n- Nama: {$booking->pelanggan->nama}\n- WA: {$booking->pelanggan->no_wa}\n\n" . + "*Detail Booking:*\n- Paket: {$paket->nama}{$txtAddons}\n" . + "- Tanggal: " . \Carbon\Carbon::parse($booking->tgl_booking)->translatedFormat('l, d F Y') . "\n" . + "- Jam: {$booking->jam_mulai} - {$jamSelesai} WIB\n" . + "- Total: Rp " . number_format($grandTotal, 0, ',', '.') . "\n\n" . + "Mohon segera diproses, ya! Terima kasih."; + + return "https://wa.me/6282337687878?text=" . urlencode($pesan); + } +} diff --git a/resources/views/admin/beranda/index.blade.php b/resources/views/admin/beranda/index.blade.php index 8f1ccdd..a394769 100755 --- a/resources/views/admin/beranda/index.blade.php +++ b/resources/views/admin/beranda/index.blade.php @@ -20,115 +20,39 @@ @endif
-
- @if (Auth::user()->role === 'pemilik') -
-
-
-
-
Pendapatan Bulan Ini
- -
-
-
- -
-
-
Rp {{ number_format($stat['pendapatan'], 0, ',', '.') }}
- - - {{ abs($stat['pendapatan_grow']) }}% - - dari bulan lalu -
-
-
-
-
- -
-
-
-
-
Total Pesanan Masuk
- -
-
-
- -
-
-
{{ $stat['masuk_count'] }} Pesanan
- - - {{ abs($stat['masuk_grow']) }}% - - dari bulan lalu -
-
-
-
-
-
-
-
-
-
Pesanan Selesai
- -
-
-
- -
-
-
{{ $stat['selesai_count'] }} Pesanan
- - {{ abs($stat['selesai_grow']) }}% - - dari bulan lalu -
-
-
-
-
-
-
-
-
-
Pesanan Ditolak
- -
-
-
- -
-
-
{{ $stat['batal_count'] }} Pesanan
- - - {{ abs($stat['batal_grow']) }}% - - dari bulan lalu -
-
-
-
-
- @endif -
- + @if (Auth::user()->role === 'pemilik') +
+ @include('admin.components._stat_card', [ + 'label' => 'Pendapatan', + 'icon' => 'bi-bank', + 'value' => 'Rp ' . number_format($stat['pendapatan'], 0, ',', '.'), + 'grow' => $stat['pendapatan_grow'], + 'tooltip' => 'Total pendapatan dari pesanan buket dan foto bulan ini', + ]) + @include('admin.components._stat_card', [ + 'label' => 'Pesanan Masuk', + 'icon' => 'bi-cart-fill', + 'value' => $stat['masuk_count'] . ' Pesanan', + 'grow' => $stat['masuk_grow'], + 'tooltip' => 'Total pesanan buket dan foto yang masuk bulan ini', + ]) + @include('admin.components._stat_card', [ + 'label' => 'Pesanan Selesai', + 'icon' => 'bi-cart-check-fill', + 'value' => $stat['selesai_count'] . ' Pesanan', + 'grow' => $stat['selesai_grow'], + 'tooltip' => 'Total pesanan buket dan foto yang selesai bulan ini', + ]) + @include('admin.components._stat_card', [ + 'label' => 'Pesanan Ditolak', + 'icon' => 'bi-cart-x-fill', + 'value' => $stat['batal_count'] . ' Pesanan', + 'grow' => $stat['batal_grow'], + 'tooltip' => 'Total pesanan buket dan foto yang ditolak bulan ini', + 'is_negative_metric' => true, + ]) +
+ @endif
@@ -201,8 +125,7 @@ class="bi {{ $stat['batal_grow'] >= 0 ? 'bi-arrow-up' : 'bi-arrow-down' }}"> {{ $p->paketFoto->nama }} + data-bs-toggle="modal" data-bs-target="#foto{{ $p->id_booking }}"> @@ -229,8 +152,7 @@ class="bi {{ $stat['batal_grow'] >= 0 ? 'bi-arrow-up' : 'bi-arrow-down' }}">