update fitur terbaru
This commit is contained in:
parent
51469f5520
commit
b68eca055b
12
.env.example
12
.env.example
|
|
@ -20,12 +20,12 @@ LOG_STACK=single
|
||||||
LOG_DEPRECATIONS_CHANNEL=null
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
DB_CONNECTION=sqlite
|
DB_CONNECTION=mysql
|
||||||
# DB_HOST=127.0.0.1
|
DB_HOST=127.0.0.1
|
||||||
# DB_PORT=3306
|
DB_PORT=3306
|
||||||
# DB_DATABASE=laravel
|
DB_DATABASE=ta_bibit_cabai
|
||||||
# DB_USERNAME=root
|
DB_USERNAME=root
|
||||||
# DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,4 @@
|
||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
vendor.zip
|
||||||
|
|
|
||||||
|
|
@ -98,20 +98,47 @@ public function login(Request $request)
|
||||||
/**
|
/**
|
||||||
* Show the admin dashboard.
|
* Show the admin dashboard.
|
||||||
*/
|
*/
|
||||||
public function dashboard()
|
public function dashboard()
|
||||||
{
|
{
|
||||||
$stats = [
|
$this->syncProductLabels();
|
||||||
'total_products' => Product::count(),
|
// Total bibit terjual dari tabel transaksi_details
|
||||||
'active_products' => Product::where('status', 'aktif')->count(),
|
$totalSold = \App\Models\TransaksiDetail::whereHas('transaksi', function($q) {
|
||||||
'out_of_stock' => Product::where('stock', 0)->count(),
|
$q->whereIn('payment_status', ['paid', 'lunas', 'success']);
|
||||||
'low_stock' => Product::where('stock', '>', 0)->where('stock', '<=', 5)->count(),
|
})->sum('quantity');
|
||||||
];
|
|
||||||
|
|
||||||
$recent_products = Product::latest()->take(5)->get();
|
// Jika masih 0, ambil semua tanpa filter payment_status
|
||||||
|
if ($totalSold == 0) {
|
||||||
return view('admin.dashboard', compact('stats', 'recent_products'));
|
$totalSold = \App\Models\TransaksiDetail::sum('quantity');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stok menipis = produk dengan stok > 0 dan stok < 1000
|
||||||
|
$lowStock = Product::where('stock', '>', 0)
|
||||||
|
->where('stock', '<', 1000)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$stats = [
|
||||||
|
'total_products' => Product::count(),
|
||||||
|
'active_products' => Product::where('status', 'aktif')->count(),
|
||||||
|
'total_sold' => number_format($totalSold, 0, ',', '.'),
|
||||||
|
'low_stock' => $lowStock,
|
||||||
|
];
|
||||||
|
|
||||||
|
$recent_products = Product::latest()->take(5)->get();
|
||||||
|
|
||||||
|
// Grafik pendapatan per hari (30 hari terakhir) - semua transaksi
|
||||||
|
$salesChart = \App\Models\Transaksi::selectRaw('DATE(created_at) as date, SUM(total_amount) as total')
|
||||||
|
->where('created_at', '>=', now()->subDays(30))
|
||||||
|
->groupBy('date')
|
||||||
|
->orderBy('date', 'asc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$chartLabels = $salesChart->pluck('date')
|
||||||
|
->map(fn($d) => \Carbon\Carbon::parse($d)->format('d M'))
|
||||||
|
->toArray();
|
||||||
|
$chartData = $salesChart->pluck('total')->toArray();
|
||||||
|
|
||||||
|
return view('admin.dashboard', compact('stats', 'recent_products', 'chartLabels', 'chartData'));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Show orders management.
|
* Show orders management.
|
||||||
*/
|
*/
|
||||||
|
|
@ -129,7 +156,16 @@ public function orders()
|
||||||
*/
|
*/
|
||||||
public function users()
|
public function users()
|
||||||
{
|
{
|
||||||
return view('admin.users');
|
$users = User::orderBy('id', 'desc')->paginate(15);
|
||||||
|
|
||||||
|
$totalUsers = User::count();
|
||||||
|
$totalAdmins = User::where('is_admin', 1)->count();
|
||||||
|
$verifiedUsers = User::whereNotNull('email_verified_at')->count();
|
||||||
|
$activeUsers = User::where('created_at', '>=', now()->subDays(30))->count();
|
||||||
|
|
||||||
|
return view('admin.users', compact(
|
||||||
|
'users', 'totalUsers', 'totalAdmins', 'verifiedUsers', 'activeUsers'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -163,12 +199,130 @@ public function logout(Request $request)
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
->with('success', 'Anda telah berhasil logout');
|
->with('success', 'Anda telah berhasil logout');
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Show reports / laporan penjualan.
|
||||||
|
* Ganti method laporan() yang ada di AdminController dengan ini.
|
||||||
|
*/
|
||||||
|
public function laporan(Request $request)
|
||||||
|
{
|
||||||
|
$startDate = $request->input('start_date', now()->startOfMonth()->toDateString());
|
||||||
|
$endDate = $request->input('end_date', now()->toDateString());
|
||||||
|
|
||||||
// Tambahkan method ini ke dalam AdminController.php Anda
|
// ── Query transaksi ───────────────────────────────────────────────
|
||||||
|
$query = \App\Models\Transaksi::with(['details.product'])
|
||||||
|
->whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']);
|
||||||
|
|
||||||
|
// Filter order_status (bukan payment_status)
|
||||||
|
if ($request->filled('status')) {
|
||||||
|
$query->where('order_status', $request->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter berdasarkan produk (lewat relasi details)
|
||||||
|
if ($request->filled('product_id')) {
|
||||||
|
$query->whereHas('details', function ($q) use ($request) {
|
||||||
|
$q->where('product_id', $request->product_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$orders = $query->orderBy('created_at', 'desc')->paginate(15);
|
||||||
|
|
||||||
|
// ── Ringkasan ─────────────────────────────────────────────────────
|
||||||
|
$summaryQuery = \App\Models\Transaksi::whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']);
|
||||||
|
if ($request->filled('status')) {
|
||||||
|
$summaryQuery->where('order_status', $request->status);
|
||||||
|
}
|
||||||
|
if ($request->filled('product_id')) {
|
||||||
|
$summaryQuery->whereHas('details', fn($q) => $q->where('product_id', $request->product_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalPendapatan = $summaryQuery->sum('total_amount');
|
||||||
|
$totalTransaksi = $summaryQuery->count();
|
||||||
|
|
||||||
|
$totalProdukKeluar = \App\Models\TransaksiDetail::whereHas('transaksi', function ($q) use ($startDate, $endDate, $request) {
|
||||||
|
$q->whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']);
|
||||||
|
if ($request->filled('status')) {
|
||||||
|
$q->where('order_status', $request->status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->when($request->filled('product_id'), fn($q) => $q->where('product_id', $request->product_id))
|
||||||
|
->sum('transaksi_details.quantity');
|
||||||
|
|
||||||
|
$summary = [
|
||||||
|
'total_pendapatan' => $totalPendapatan,
|
||||||
|
'total_transaksi' => $totalTransaksi,
|
||||||
|
'total_produk_keluar' => $totalProdukKeluar,
|
||||||
|
'rata_per_transaksi' => $totalTransaksi > 0 ? round($totalPendapatan / $totalTransaksi) : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Produk terlaris ───────────────────────────────────────────────
|
||||||
|
$productSales = \App\Models\TransaksiDetail::selectRaw('
|
||||||
|
transaksi_details.product_id,
|
||||||
|
products.name as product_name,
|
||||||
|
SUM(transaksi_details.quantity) as total_qty,
|
||||||
|
SUM(transaksi_details.subtotal) as total_revenue
|
||||||
|
')
|
||||||
|
->join('products', 'transaksi_details.product_id', '=', 'products.id')
|
||||||
|
->whereHas('transaksi', function ($q) use ($startDate, $endDate, $request) {
|
||||||
|
$q->whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']);
|
||||||
|
if ($request->filled('status')) {
|
||||||
|
$q->where('order_status', $request->status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->when($request->filled('product_id'), fn($q) => $q->where('transaksi_details.product_id', $request->product_id))
|
||||||
|
->groupBy('transaksi_details.product_id', 'products.name')
|
||||||
|
->orderByDesc('total_qty')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$topProducts = $productSales->take(7);
|
||||||
|
|
||||||
|
// ── Grafik harian ─────────────────────────────────────────────────
|
||||||
|
$salesChart = \App\Models\Transaksi::selectRaw('DATE(created_at) as date, SUM(total_amount) as total')
|
||||||
|
->whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59'])
|
||||||
|
->when($request->filled('status'), fn($q) => $q->where('order_status', $request->status))
|
||||||
|
->when($request->filled('product_id'), fn($q) => $q->whereHas('details', fn($d) => $d->where('product_id', $request->product_id)))
|
||||||
|
->groupBy('date')
|
||||||
|
->orderBy('date', 'asc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$chartLabels = $salesChart->pluck('date')->map(fn($d) => \Carbon\Carbon::parse($d)->format('d M'))->toArray();
|
||||||
|
$chartData = $salesChart->pluck('total')->toArray();
|
||||||
|
|
||||||
|
$products = \App\Models\Product::orderBy('name')->get();
|
||||||
|
|
||||||
|
return view('admin.laporan', compact(
|
||||||
|
'orders', 'summary', 'productSales', 'topProducts',
|
||||||
|
'chartLabels', 'chartData', 'products'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display orders page with shipping and status information
|
* Sync label produk berdasarkan total terjual dari transaksi nyata
|
||||||
*/
|
*/
|
||||||
|
private function syncProductLabels()
|
||||||
|
{
|
||||||
|
// Ambil top 10 produk terlaris berdasarkan transaksi nyata
|
||||||
|
$bestSellingIds = \Illuminate\Support\Facades\DB::table('transaksi_details')
|
||||||
|
->select('product_id', \Illuminate\Support\Facades\DB::raw('SUM(quantity) as total_sold'))
|
||||||
|
->groupBy('product_id')
|
||||||
|
->orderByDesc('total_sold')
|
||||||
|
->take(10)
|
||||||
|
->pluck('product_id')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
// Reset semua label dulu
|
||||||
|
Product::where('stock', '>', 0)
|
||||||
|
->whereNotIn('id', $bestSellingIds)
|
||||||
|
->update(['label' => 'tersedia']);
|
||||||
|
|
||||||
|
Product::where('stock', '<=', 0)
|
||||||
|
->update(['label' => 'habis']);
|
||||||
|
|
||||||
|
// Set label terlaris untuk top 10
|
||||||
|
if (!empty($bestSellingIds)) {
|
||||||
|
Product::whereIn('id', $bestSellingIds)
|
||||||
|
->where('stock', '>', 0)
|
||||||
|
->update(['label' => 'terlaris']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class AdminForgotPasswordController extends Controller
|
||||||
|
{
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
// STEP 1 — Tampilkan form input email
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
public function showForgotForm()
|
||||||
|
{
|
||||||
|
return view('admin.forgot-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
// STEP 1 — Proses: validasi email & kirim OTP
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
public function sendOtp(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'email' => ['required', 'email'],
|
||||||
|
], [
|
||||||
|
'email.required' => 'Email wajib diisi.',
|
||||||
|
'email.email' => 'Format email tidak valid.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$admin = User::where('email', $request->email)
|
||||||
|
->where('is_admin', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$admin) {
|
||||||
|
return back()->withErrors([
|
||||||
|
'email' => 'Email tidak terdaftar sebagai akun admin.',
|
||||||
|
])->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
DB::table('password_reset_tokens')->where('email', $request->email)->delete();
|
||||||
|
DB::table('password_reset_tokens')->insert([
|
||||||
|
'email' => $request->email,
|
||||||
|
'token' => Hash::make($otp),
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Mail::send('emails.admin-otp', [
|
||||||
|
'otp' => $otp,
|
||||||
|
'name' => $admin->name ?? 'Admin',
|
||||||
|
], function ($message) use ($request) {
|
||||||
|
$message->to($request->email)
|
||||||
|
->subject('Kode Verifikasi Reset Password Admin - Bibit Cabai');
|
||||||
|
});
|
||||||
|
|
||||||
|
$request->session()->put('admin_reset_email', $request->email);
|
||||||
|
|
||||||
|
return redirect()->route('admin.password.verify-otp-form')
|
||||||
|
->with('success', 'Kode OTP telah dikirim ke email admin Anda. Berlaku 10 menit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
// STEP 2 — Tampilkan form input OTP
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
public function showVerifyOtpForm(Request $request)
|
||||||
|
{
|
||||||
|
if (!$request->session()->has('admin_reset_email')) {
|
||||||
|
return redirect()->route('admin.password.request')
|
||||||
|
->with('error', 'Silakan masukkan email admin terlebih dahulu.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin.verify-otp');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
// STEP 2 — Proses: verifikasi OTP
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
public function verifyOtp(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'otp' => ['required', 'digits:6'],
|
||||||
|
], [
|
||||||
|
'otp.required' => 'Kode OTP wajib diisi.',
|
||||||
|
'otp.digits' => 'Kode OTP harus 6 digit angka.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$email = $request->session()->get('admin_reset_email');
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
return redirect()->route('admin.password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = DB::table('password_reset_tokens')->where('email', $email)->first();
|
||||||
|
|
||||||
|
if (!$record) {
|
||||||
|
return back()->with('error', 'Kode OTP tidak ditemukan. Silakan kirim ulang.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Carbon::now()->diffInMinutes(Carbon::parse($record->created_at)) > 10) {
|
||||||
|
DB::table('password_reset_tokens')->where('email', $email)->delete();
|
||||||
|
return back()->with('error', 'Kode OTP sudah kadaluarsa. Silakan kirim ulang kode.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Hash::check($request->otp, $record->token)) {
|
||||||
|
return back()->with('error', 'Kode OTP salah. Periksa kembali kode yang Anda masukkan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$verifiedToken = Str::random(60);
|
||||||
|
DB::table('password_reset_tokens')
|
||||||
|
->where('email', $email)
|
||||||
|
->update(['token' => Hash::make($verifiedToken)]);
|
||||||
|
|
||||||
|
$request->session()->put('admin_reset_token', $verifiedToken);
|
||||||
|
|
||||||
|
return redirect()->route('admin.password.reset-form')
|
||||||
|
->with('success', 'Kode berhasil diverifikasi! Silakan buat password baru.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
// STEP 2 — Kirim ulang OTP
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
public function resendOtp(Request $request)
|
||||||
|
{
|
||||||
|
$email = $request->session()->get('admin_reset_email');
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
return redirect()->route('admin.password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan masukkan email admin kembali.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$admin = User::where('email', $email)
|
||||||
|
->where('is_admin', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$admin) {
|
||||||
|
return redirect()->route('admin.password.request')
|
||||||
|
->with('error', 'Email tidak ditemukan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
DB::table('password_reset_tokens')->where('email', $email)->delete();
|
||||||
|
DB::table('password_reset_tokens')->insert([
|
||||||
|
'email' => $email,
|
||||||
|
'token' => Hash::make($otp),
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Mail::send('emails.admin-otp', [
|
||||||
|
'otp' => $otp,
|
||||||
|
'name' => $admin->name ?? 'Admin',
|
||||||
|
], function ($message) use ($email) {
|
||||||
|
$message->to($email)
|
||||||
|
->subject('Kode OTP Baru (Kirim Ulang) - Bibit Cabai Admin');
|
||||||
|
});
|
||||||
|
|
||||||
|
return back()->with('success', 'Kode OTP baru telah dikirim ke email admin Anda.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
// STEP 3 — Tampilkan form password baru
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
public function showResetForm(Request $request)
|
||||||
|
{
|
||||||
|
if (!$request->session()->has('admin_reset_token') ||
|
||||||
|
!$request->session()->has('admin_reset_email')) {
|
||||||
|
return redirect()->route('admin.password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin.reset-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
// STEP 3 — Proses: simpan password baru
|
||||||
|
// ─────────────────────────────────────────────────────────
|
||||||
|
public function resetPassword(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'password' => ['required', 'min:8', 'confirmed', 'regex:/^\S+$/'],
|
||||||
|
'password_confirmation' => ['required'],
|
||||||
|
], [
|
||||||
|
'password.required' => 'Password baru wajib diisi.',
|
||||||
|
'password.min' => 'Password minimal 8 karakter.',
|
||||||
|
'password.confirmed' => 'Konfirmasi password tidak cocok.',
|
||||||
|
'password.regex' => 'Password tidak boleh mengandung spasi.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$email = $request->session()->get('admin_reset_email');
|
||||||
|
$token = $request->session()->get('admin_reset_token');
|
||||||
|
|
||||||
|
if (!$email || !$token) {
|
||||||
|
return redirect()->route('admin.password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = DB::table('password_reset_tokens')->where('email', $email)->first();
|
||||||
|
|
||||||
|
if (!$record || !Hash::check($token, $record->token)) {
|
||||||
|
return redirect()->route('admin.password.request')
|
||||||
|
->with('error', 'Token tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
User::where('email', $email)
|
||||||
|
->where('is_admin', true)
|
||||||
|
->update(['password' => Hash::make($request->password)]);
|
||||||
|
|
||||||
|
DB::table('password_reset_tokens')->where('email', $email)->delete();
|
||||||
|
$request->session()->forget(['admin_reset_email', 'admin_reset_token']);
|
||||||
|
|
||||||
|
return redirect()->route('admin.login.form')
|
||||||
|
->with('success', 'Password admin berhasil diubah! Silakan login dengan password baru.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class ForgotPasswordController extends Controller
|
||||||
|
{
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// STEP 1 — Show form: enter email
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
public function showForgotForm()
|
||||||
|
{
|
||||||
|
return view('auth.forgot-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// STEP 1 — Handle: send OTP to email
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
public function sendOtp(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'email' => ['required', 'email', 'regex:/@gmail\.com$/'],
|
||||||
|
], [
|
||||||
|
'email.required' => 'Email wajib diisi.',
|
||||||
|
'email.email' => 'Format email tidak valid.',
|
||||||
|
'email.regex' => 'Email harus menggunakan domain @gmail.com.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::where('email', $request->email)->first();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return back()->withErrors([
|
||||||
|
'email' => 'Email tidak ditemukan di sistem kami.',
|
||||||
|
])->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate 6-digit OTP
|
||||||
|
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
// Delete any existing token for this email
|
||||||
|
DB::table('password_reset_tokens')->where('email', $request->email)->delete();
|
||||||
|
|
||||||
|
// Store hashed OTP + expiry (10 minutes)
|
||||||
|
DB::table('password_reset_tokens')->insert([
|
||||||
|
'email' => $request->email,
|
||||||
|
'token' => Hash::make($otp),
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Send OTP via email
|
||||||
|
Mail::send('emails.otp', ['otp' => $otp, 'name' => $user->name], function ($message) use ($request) {
|
||||||
|
$message->to($request->email)
|
||||||
|
->subject('Kode Verifikasi Reset Password - Bibit Cabai Bondowoso');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store email in session for next steps
|
||||||
|
$request->session()->put('reset_email', $request->email);
|
||||||
|
|
||||||
|
return redirect()->route('password.verify-otp-form')
|
||||||
|
->with('success', 'Kode verifikasi telah dikirim ke email Anda. Berlaku selama 10 menit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// STEP 2 — Show form: enter OTP
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
public function showVerifyOtpForm(Request $request)
|
||||||
|
{
|
||||||
|
if (!$request->session()->has('reset_email')) {
|
||||||
|
return redirect()->route('password.request')
|
||||||
|
->with('error', 'Silakan masukkan email Anda terlebih dahulu.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('auth.verify-otp');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// STEP 2 — Handle: verify OTP
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
public function verifyOtp(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'otp' => ['required', 'digits:6'],
|
||||||
|
], [
|
||||||
|
'otp.required' => 'Kode OTP wajib diisi.',
|
||||||
|
'otp.digits' => 'Kode OTP harus 6 digit angka.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$email = $request->session()->get('reset_email');
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
return redirect()->route('password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = DB::table('password_reset_tokens')
|
||||||
|
->where('email', $email)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$record) {
|
||||||
|
return back()->with('error', 'Kode OTP tidak ditemukan. Silakan kirim ulang.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check expiry: 10 minutes
|
||||||
|
$createdAt = Carbon::parse($record->created_at);
|
||||||
|
if (Carbon::now()->diffInMinutes($createdAt) > 10) {
|
||||||
|
DB::table('password_reset_tokens')->where('email', $email)->delete();
|
||||||
|
return back()->with('error', 'Kode OTP sudah kadaluarsa. Silakan kirim ulang kode.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify OTP
|
||||||
|
if (!Hash::check($request->otp, $record->token)) {
|
||||||
|
return back()->with('error', 'Kode OTP salah. Periksa kembali kode yang Anda masukkan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a verified token for the next step
|
||||||
|
$verifiedToken = Str::random(60);
|
||||||
|
DB::table('password_reset_tokens')
|
||||||
|
->where('email', $email)
|
||||||
|
->update(['token' => Hash::make($verifiedToken)]);
|
||||||
|
|
||||||
|
$request->session()->put('reset_token', $verifiedToken);
|
||||||
|
|
||||||
|
return redirect()->route('password.reset-form')
|
||||||
|
->with('success', 'Kode berhasil diverifikasi! Silakan buat password baru.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// STEP 2 — Handle: resend OTP
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
public function resendOtp(Request $request)
|
||||||
|
{
|
||||||
|
$email = $request->session()->get('reset_email');
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
return redirect()->route('password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan masukkan email Anda kembali.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::where('email', $email)->first();
|
||||||
|
if (!$user) {
|
||||||
|
return redirect()->route('password.request')
|
||||||
|
->with('error', 'Email tidak ditemukan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
DB::table('password_reset_tokens')->where('email', $email)->delete();
|
||||||
|
DB::table('password_reset_tokens')->insert([
|
||||||
|
'email' => $email,
|
||||||
|
'token' => Hash::make($otp),
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Mail::send('emails.otp', ['otp' => $otp, 'name' => $user->name], function ($message) use ($email) {
|
||||||
|
$message->to($email)
|
||||||
|
->subject('Kode Verifikasi Reset Password (Kirim Ulang) - Bibit Cabai Bondowoso');
|
||||||
|
});
|
||||||
|
|
||||||
|
return back()->with('success', 'Kode OTP baru telah dikirim ke email Anda.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// STEP 3 — Show form: enter new password
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
public function showResetForm(Request $request)
|
||||||
|
{
|
||||||
|
if (!$request->session()->has('reset_token') || !$request->session()->has('reset_email')) {
|
||||||
|
return redirect()->route('password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('auth.reset-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// STEP 3 — Handle: save new password
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
public function resetPassword(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'password' => ['required', 'min:8', 'confirmed', 'regex:/^\S+$/'],
|
||||||
|
'password_confirmation' => ['required'],
|
||||||
|
], [
|
||||||
|
'password.required' => 'Password baru wajib diisi.',
|
||||||
|
'password.min' => 'Password minimal 8 karakter.',
|
||||||
|
'password.confirmed' => 'Konfirmasi password tidak cocok.',
|
||||||
|
'password.regex' => 'Password tidak boleh mengandung spasi.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$email = $request->session()->get('reset_email');
|
||||||
|
$token = $request->session()->get('reset_token');
|
||||||
|
|
||||||
|
if (!$email || !$token) {
|
||||||
|
return redirect()->route('password.request')
|
||||||
|
->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = DB::table('password_reset_tokens')->where('email', $email)->first();
|
||||||
|
|
||||||
|
if (!$record || !Hash::check($token, $record->token)) {
|
||||||
|
return redirect()->route('password.request')
|
||||||
|
->with('error', 'Token tidak valid. Silakan ulangi dari awal.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update password
|
||||||
|
User::where('email', $email)->update([
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
DB::table('password_reset_tokens')->where('email', $email)->delete();
|
||||||
|
$request->session()->forget(['reset_email', 'reset_token']);
|
||||||
|
|
||||||
|
return redirect()->route('login')
|
||||||
|
->with('success', 'Password berhasil diubah! Silakan login dengan password baru Anda.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Mail\AdminOrderNotification;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
class CheckoutController extends Controller
|
class CheckoutController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -46,90 +48,152 @@ public function index(Request $request)
|
||||||
}
|
}
|
||||||
|
|
||||||
public function process(Request $request)
|
public function process(Request $request)
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$request->validate([
|
||||||
'product_id' => 'required|exists:products,id',
|
'product_id' => 'required|exists:products,id',
|
||||||
'quantity' => 'required|integer|min:1',
|
'quantity' => 'required|integer|min:1',
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|min:8|max:255',
|
||||||
'phone' => 'required|string|max:20',
|
'phone' => ['required', 'string', 'min:11', 'regex:/^\+62[0-9]{8,}$/'],
|
||||||
'email' => 'required|email|max:255',
|
'email' => 'required|email|min:8|max:255',
|
||||||
'address' => 'required|string',
|
'address' => ['required', 'string', 'min:20',
|
||||||
'city' => 'required|string|max:100',
|
'regex:/(?=.*[Jj]l\.?|.*[Jj]alan)(?=.*[Nn]o\.?)(?=.*[Rr][Tt])(?=.*[Rr][Ww])/'],
|
||||||
'postal_code' => 'required|string|max:10',
|
'city' => 'required|string|max:100',
|
||||||
'payment_method' => 'required|in:qris,bri,dana,seabank,shopee,cod', // TAMBAHKAN 'qris'
|
'postal_code' => 'required|string|max:10',
|
||||||
'shipping_cost' => 'required|numeric|min:0',
|
'payment_method' => 'required|in:qris,bri,dana,seabank,shopee,cod',
|
||||||
'notes' => 'nullable|string|max:1000'
|
'shipping_cost' => 'required|numeric|min:0',
|
||||||
]);
|
'notes' => 'nullable|string|max:1000',
|
||||||
|
], [
|
||||||
|
'name.required' => 'Nama lengkap wajib diisi.',
|
||||||
|
'name.min' => 'Nama lengkap minimal 8 karakter.',
|
||||||
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
|
'phone.min' => 'Nomor telepon minimal 11 karakter.',
|
||||||
|
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya berisi angka. Contoh: +6281234567890',
|
||||||
|
'email.required' => 'Email wajib diisi.',
|
||||||
|
'email.email' => 'Format email tidak valid.',
|
||||||
|
'email.min' => 'Email minimal 8 karakter.',
|
||||||
|
'address.required' => 'Alamat lengkap wajib diisi.',
|
||||||
|
'address.min' => 'Alamat terlalu pendek, harap isi lengkap.',
|
||||||
|
'address.regex' => 'Alamat harus lengkap: Nama Jalan, No. Rumah, RT/RW. Contoh: Jl. Merdeka No. 12, RT 02/RW 05, Desa Kademangan',
|
||||||
|
'city.required' => 'Kecamatan wajib dipilih.',
|
||||||
|
'postal_code.required' => 'Kode pos wajib diisi.',
|
||||||
|
'payment_method.required'=> 'Metode pembayaran wajib dipilih.',
|
||||||
|
'shipping_cost.required' => 'Ongkos kirim belum dihitung, pilih kecamatan terlebih dahulu.',
|
||||||
|
]);
|
||||||
|
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$product = Product::lockForUpdate()->find($request->product_id);
|
$product = Product::lockForUpdate()->find($request->product_id);
|
||||||
|
|
||||||
if (!$product) {
|
if (!$product) {
|
||||||
throw new \Exception('Produk tidak ditemukan');
|
throw new \Exception('Produk tidak ditemukan');
|
||||||
}
|
|
||||||
|
|
||||||
if ($product->stock < $request->quantity) {
|
|
||||||
throw new \Exception('Stok tidak mencukupi. Stok tersedia: ' . $product->stock);
|
|
||||||
}
|
|
||||||
|
|
||||||
$subtotal = $product->price * $request->quantity;
|
|
||||||
$ongkir = 15000;
|
|
||||||
$total = $subtotal + $ongkir;
|
|
||||||
|
|
||||||
$invoiceNumber = 'INV-' . date('Ymd') . '-' . strtoupper(substr(uniqid(), -6));
|
|
||||||
|
|
||||||
$transaksi = new Transaksi();
|
|
||||||
$transaksi->invoice_number = $invoiceNumber;
|
|
||||||
$transaksi->user_id = Auth::id();
|
|
||||||
$transaksi->customer_name = $request->name;
|
|
||||||
$transaksi->customer_phone = $request->phone;
|
|
||||||
$transaksi->customer_email = $request->email;
|
|
||||||
$transaksi->shipping_address = $request->address;
|
|
||||||
$transaksi->province = $request->province;
|
|
||||||
$transaksi->city = $request->city;
|
|
||||||
$transaksi->postal_code = $request->postal_code;
|
|
||||||
$transaksi->subtotal = $subtotal;
|
|
||||||
$transaksi->shipping_cost = $ongkir;
|
|
||||||
$transaksi->total_amount = $total;
|
|
||||||
$transaksi->payment_method = $request->payment_method;
|
|
||||||
$transaksi->payment_status = 'pending';
|
|
||||||
$transaksi->order_status = 'pending';
|
|
||||||
$transaksi->notes = $request->notes;
|
|
||||||
$transaksi->save();
|
|
||||||
|
|
||||||
$detail = new TransaksiDetail();
|
|
||||||
$detail->transaksi_id = $transaksi->id;
|
|
||||||
$detail->product_id = $product->id;
|
|
||||||
$detail->product_name = $product->name;
|
|
||||||
$detail->quantity = $request->quantity;
|
|
||||||
$detail->price = $product->price;
|
|
||||||
$detail->subtotal = $subtotal;
|
|
||||||
$detail->save();
|
|
||||||
|
|
||||||
$product->stock = $product->stock - $request->quantity;
|
|
||||||
$product->save();
|
|
||||||
|
|
||||||
DB::commit();
|
|
||||||
|
|
||||||
return redirect()->route('checkout.success', ['id' => $transaksi->id])
|
|
||||||
->with('success', 'Pesanan berhasil dibuat!');
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
DB::rollBack();
|
|
||||||
|
|
||||||
Log::error('Checkout Process Error', [
|
|
||||||
'message' => $e->getMessage(),
|
|
||||||
'file' => $e->getFile(),
|
|
||||||
'line' => $e->getLine()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return redirect()->back()
|
|
||||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage())
|
|
||||||
->withInput();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($product->stock < $request->quantity) {
|
||||||
|
throw new \Exception('Stok tidak mencukupi. Stok tersedia: ' . $product->stock);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ongkirData = [
|
||||||
|
'Bondowoso' => 20000,
|
||||||
|
'Grujugan' => 30000,
|
||||||
|
'Jambesari Darus Sholah' => 250000,
|
||||||
|
'Klabang' => 10000,
|
||||||
|
'Tenggarang' => 12000,
|
||||||
|
'Binakal' => 12000,
|
||||||
|
'Prajekan' => 13000,
|
||||||
|
'Botolinggo' => 13000,
|
||||||
|
'Maesan' => 50000,
|
||||||
|
'Tamanan' => 40000,
|
||||||
|
'Wonosari' => 10000,
|
||||||
|
'Pujer' => 15000,
|
||||||
|
'Tlogosari' => 250000,
|
||||||
|
'Sukosari' => 18000,
|
||||||
|
'Sumberwringin' => 20000,
|
||||||
|
'Tegalampel' => 30000,
|
||||||
|
'Sempol' => 80000,
|
||||||
|
'Pakem' => 50000,
|
||||||
|
'Curahdami' => 40000,
|
||||||
|
'Ijen' => 100000,
|
||||||
|
'Tapen' => 10000,
|
||||||
|
'Wringin' => 300000,
|
||||||
|
'Taman Krocok' => 28000,
|
||||||
|
];
|
||||||
|
|
||||||
|
$subtotal = $product->price * $request->quantity;
|
||||||
|
$baseOngkir = $ongkirData[$request->city] ?? 15000;
|
||||||
|
$ongkir = $baseOngkir;
|
||||||
|
|
||||||
|
$diskon = 0;
|
||||||
|
if ($request->quantity >= 1000) {
|
||||||
|
$ongkir = 0;
|
||||||
|
$diskon = $subtotal * 0.15;
|
||||||
|
} elseif ($request->quantity >= 800) {
|
||||||
|
$ongkir = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = ($subtotal - $diskon) + $ongkir;
|
||||||
|
|
||||||
|
$invoiceNumber = 'INV-' . date('Ymd') . '-' . strtoupper(substr(uniqid(), -6));
|
||||||
|
|
||||||
|
$transaksi = new Transaksi();
|
||||||
|
$transaksi->invoice_number = $invoiceNumber;
|
||||||
|
$transaksi->user_id = Auth::id();
|
||||||
|
$transaksi->customer_name = $request->name;
|
||||||
|
$transaksi->customer_phone = $request->phone;
|
||||||
|
$transaksi->customer_email = $request->email;
|
||||||
|
$transaksi->shipping_address= $request->address;
|
||||||
|
$transaksi->province = $request->province;
|
||||||
|
$transaksi->city = $request->city;
|
||||||
|
$transaksi->postal_code = $request->postal_code;
|
||||||
|
$transaksi->subtotal = $subtotal;
|
||||||
|
$transaksi->discount = $diskon;
|
||||||
|
$transaksi->shipping_cost = $ongkir;
|
||||||
|
$transaksi->total_amount = $total;
|
||||||
|
$transaksi->payment_method = $request->payment_method;
|
||||||
|
$transaksi->payment_status = 'pending';
|
||||||
|
$transaksi->order_status = 'pending';
|
||||||
|
$transaksi->notes = $request->notes;
|
||||||
|
$transaksi->save();
|
||||||
|
|
||||||
|
$detail = new TransaksiDetail();
|
||||||
|
$detail->transaksi_id = $transaksi->id;
|
||||||
|
$detail->product_id = $product->id;
|
||||||
|
$detail->product_name = $product->name;
|
||||||
|
$detail->quantity = $request->quantity;
|
||||||
|
$detail->price = $product->price;
|
||||||
|
$detail->subtotal = $subtotal;
|
||||||
|
$detail->save();
|
||||||
|
|
||||||
|
$product->stock = $product->stock - $request->quantity;
|
||||||
|
$product->save();
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
// Kirim notifikasi email ke admin
|
||||||
|
try {
|
||||||
|
Mail::to(config('mail.admin_email', 'bcom0508@gmail.com'))
|
||||||
|
->send(new AdminOrderNotification($transaksi->load('details')));
|
||||||
|
} catch (\Exception $mailError) {
|
||||||
|
Log::warning('Gagal kirim notifikasi email: ' . $mailError->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('checkout.success', ['id' => $transaksi->id])
|
||||||
|
->with('success', 'Pesanan berhasil dibuat!');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
Log::error('Checkout Process Error', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back()
|
||||||
|
->with('error', 'Terjadi kesalahan: ' . $e->getMessage())
|
||||||
|
->withInput();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function success($id)
|
public function success($id)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Contact;
|
use App\Models\Contact;
|
||||||
|
use App\Mail\ContactMail;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
class ContactController extends Controller
|
class ContactController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -14,18 +16,70 @@ public function index(): View
|
||||||
return view('contact');
|
return view('contact');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => [
|
||||||
'email' => 'required|email|max:255',
|
'required',
|
||||||
'phone' => 'nullable|string|max:20',
|
'string',
|
||||||
'subject' => 'required|string|max:255',
|
'min:10',
|
||||||
'message' => 'required|string|max:1000',
|
'max:255',
|
||||||
]);
|
],
|
||||||
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'email',
|
||||||
|
'min:8',
|
||||||
|
'max:255',
|
||||||
|
'regex:/^[a-zA-Z0-9._%+\-]+@gmail\.com$/',
|
||||||
|
],
|
||||||
|
'phone' => [
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
'min:11',
|
||||||
|
'max:20',
|
||||||
|
'regex:/^[0-9]+$/',
|
||||||
|
],
|
||||||
|
'subject' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'min:8',
|
||||||
|
'max:255',
|
||||||
|
],
|
||||||
|
'message' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
function ($attribute, $value, $fail) {
|
||||||
|
$wordCount = str_word_count($value);
|
||||||
|
if ($wordCount < 5) {
|
||||||
|
$fail('Pesan harus terdiri dari minimal 5 kata.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
], [
|
||||||
|
// Pesan error custom dalam Bahasa Indonesia
|
||||||
|
'name.required' => 'Nama lengkap wajib diisi.',
|
||||||
|
'name.min' => 'Nama lengkap minimal 10 karakter.',
|
||||||
|
|
||||||
Contact::create($validated);
|
'email.required' => 'Email wajib diisi.',
|
||||||
|
'email.min' => 'Email minimal 8 karakter.',
|
||||||
|
'email.email' => 'Format email tidak valid.',
|
||||||
|
'email.regex' => 'Email harus menggunakan @gmail.com.',
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Pesan Anda telah terkirim! Kami akan segera merespons.');
|
'phone.min' => 'No. telepon minimal 11 karakter.',
|
||||||
}
|
'phone.regex' => 'No. telepon hanya boleh berisi angka.',
|
||||||
|
|
||||||
|
'subject.required' => 'Subjek wajib diisi.',
|
||||||
|
'subject.min' => 'Subjek minimal 8 karakter.',
|
||||||
|
|
||||||
|
'message.required' => 'Pesan wajib diisi.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Simpan ke database
|
||||||
|
Contact::create($validated);
|
||||||
|
|
||||||
|
// Kirim email
|
||||||
|
Mail::to('bayualghozali86@gmail.com')->send(new ContactMail($validated));
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Pesan Anda telah terkirim! Kami akan segera merespons.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,19 +4,41 @@
|
||||||
|
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// Get featured products (best selling or marked as featured)
|
$featuredProducts = DB::table('transaksi_details')
|
||||||
$featuredProducts = Product::active()
|
->join('transaksis', 'transaksi_details.transaksi_id', '=', 'transaksis.id')
|
||||||
->orderByDesc('sold') // Order by most sold
|
->join('products', 'transaksi_details.product_id', '=', 'products.id')
|
||||||
->take(6)
|
->where('products.status', 'aktif')
|
||||||
->get();
|
->select(
|
||||||
|
'products.id',
|
||||||
|
'products.name',
|
||||||
|
'products.image',
|
||||||
|
'products.price',
|
||||||
|
'products.stock',
|
||||||
|
DB::raw('SUM(transaksi_details.quantity) as total_sold'),
|
||||||
|
DB::raw('COUNT(DISTINCT transaksis.id) as total_orders')
|
||||||
|
)
|
||||||
|
->groupBy(
|
||||||
|
'products.id',
|
||||||
|
'products.name',
|
||||||
|
'products.image',
|
||||||
|
'products.price',
|
||||||
|
'products.stock'
|
||||||
|
)
|
||||||
|
->orderByDesc('total_sold')
|
||||||
|
->take(2)
|
||||||
|
->get();
|
||||||
|
|
||||||
return view('home', compact('featuredProducts'));
|
// Ambil semua produk aktif
|
||||||
}
|
$allProducts = Product::active()->orderByDesc('created_at')->get();
|
||||||
|
|
||||||
|
return view('home', compact('featuredProducts', 'allProducts'));
|
||||||
|
}
|
||||||
|
|
||||||
public function bestSellingProducts()
|
public function bestSellingProducts()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,9 @@ public function store(Request $request)
|
||||||
$product = Product::create($data);
|
$product = Product::create($data);
|
||||||
|
|
||||||
// Auto set label based on stock
|
// Auto set label based on stock
|
||||||
$product->updateLabelBasedOnStock();
|
$product->updateLabelBasedOnStock();
|
||||||
$product->save();
|
$product->save();
|
||||||
|
$this->syncLabelAfterSave($product);
|
||||||
|
|
||||||
return redirect()->route('admin.products.index')
|
return redirect()->route('admin.products.index')
|
||||||
->with('success', 'Produk berhasil ditambahkan!');
|
->with('success', 'Produk berhasil ditambahkan!');
|
||||||
|
|
@ -165,8 +166,9 @@ public function update(Request $request, Product $product)
|
||||||
$product->update($data);
|
$product->update($data);
|
||||||
|
|
||||||
// Auto update label based on new stock
|
// Auto update label based on new stock
|
||||||
$product->updateLabelBasedOnStock();
|
$product->updateLabelBasedOnStock();
|
||||||
$product->save();
|
$product->save();
|
||||||
|
$this->syncLabelAfterSave($product);
|
||||||
|
|
||||||
return redirect()->route('admin.products.index')
|
return redirect()->route('admin.products.index')
|
||||||
->with('success', 'Produk berhasil diupdate!');
|
->with('success', 'Produk berhasil diupdate!');
|
||||||
|
|
@ -195,7 +197,12 @@ public function destroy(Product $product)
|
||||||
return redirect()->route('admin.products.index')
|
return redirect()->route('admin.products.index')
|
||||||
->with('success', 'Produk berhasil dihapus');
|
->with('success', 'Produk berhasil dihapus');
|
||||||
}
|
}
|
||||||
|
// Tampilkan detail produk untuk user (public)
|
||||||
|
public function publicShow($id)
|
||||||
|
{
|
||||||
|
$product = Product::where('status', 'aktif')->findOrFail($id);
|
||||||
|
return view('products.show', compact('product'));
|
||||||
|
}
|
||||||
// NEW: Method untuk debugging gambar
|
// NEW: Method untuk debugging gambar
|
||||||
public function debugImage($id)
|
public function debugImage($id)
|
||||||
{
|
{
|
||||||
|
|
@ -212,4 +219,27 @@ public function debugImage($id)
|
||||||
|
|
||||||
return response()->json($debug);
|
return response()->json($debug);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Cek apakah produk ini termasuk top 10 terlaris, jika iya set label terlaris
|
||||||
|
*/
|
||||||
|
private function syncLabelAfterSave(Product $product)
|
||||||
|
{
|
||||||
|
if ($product->stock <= 0) {
|
||||||
|
return; // Sudah di-handle updateLabelBasedOnStock
|
||||||
|
}
|
||||||
|
|
||||||
|
$bestSellingIds = \Illuminate\Support\Facades\DB::table('transaksi_details')
|
||||||
|
->select('product_id', \Illuminate\Support\Facades\DB::raw('SUM(quantity) as total_sold'))
|
||||||
|
->groupBy('product_id')
|
||||||
|
->orderByDesc('total_sold')
|
||||||
|
->take(10)
|
||||||
|
->pluck('product_id')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (in_array($product->id, $bestSellingIds)) {
|
||||||
|
$product->label = 'terlaris';
|
||||||
|
$product->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,33 +7,15 @@
|
||||||
use App\Models\Transaksi;
|
use App\Models\Transaksi;
|
||||||
use App\Models\TransaksiDetail;
|
use App\Models\TransaksiDetail;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class ProductRecommendationController extends Controller
|
class ProductRecommendationController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* VERSI UNTUK DEBUGGING
|
|
||||||
* Menampilkan produk terlaris dengan informasi lengkap untuk debugging
|
|
||||||
*/
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// Log untuk debugging
|
// Query produk terlaris dari SEMUA status transaksi
|
||||||
Log::info('🔍 Mengakses halaman produk terlaris');
|
|
||||||
|
|
||||||
// Cek total transaksi
|
|
||||||
$totalTransaksi = Transaksi::count();
|
|
||||||
$totalDelivered = Transaksi::where('order_status', 'delivered')->count();
|
|
||||||
$totalDetails = TransaksiDetail::count();
|
|
||||||
|
|
||||||
Log::info("📊 Total Transaksi: {$totalTransaksi}");
|
|
||||||
Log::info("✅ Delivered: {$totalDelivered}");
|
|
||||||
Log::info("📦 Detail: {$totalDetails}");
|
|
||||||
|
|
||||||
// Query produk terlaris HANYA dari transaksi delivered
|
|
||||||
$bestSellingProducts = DB::table('transaksi_details')
|
$bestSellingProducts = DB::table('transaksi_details')
|
||||||
->join('transaksis', 'transaksi_details.transaksi_id', '=', 'transaksis.id')
|
->join('transaksis', 'transaksi_details.transaksi_id', '=', 'transaksis.id')
|
||||||
->join('products', 'transaksi_details.product_id', '=', 'products.id')
|
->join('products', 'transaksi_details.product_id', '=', 'products.id')
|
||||||
->where('transaksis.order_status', 'delivered')
|
|
||||||
->where('products.status', 'aktif')
|
->where('products.status', 'aktif')
|
||||||
->select(
|
->select(
|
||||||
'products.id',
|
'products.id',
|
||||||
|
|
@ -55,72 +37,21 @@ public function index()
|
||||||
->take(10)
|
->take(10)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
Log::info("🏆 Produk terlaris ditemukan: " . $bestSellingProducts->count());
|
|
||||||
|
|
||||||
// FALLBACK: Jika tidak ada produk delivered, tampilkan dari semua status
|
|
||||||
if ($bestSellingProducts->isEmpty()) {
|
|
||||||
Log::warning('⚠️ Tidak ada produk dari transaksi delivered, menggunakan semua status');
|
|
||||||
|
|
||||||
$bestSellingProducts = DB::table('transaksi_details')
|
|
||||||
->join('transaksis', 'transaksi_details.transaksi_id', '=', 'transaksis.id')
|
|
||||||
->join('products', 'transaksi_details.product_id', '=', 'products.id')
|
|
||||||
// TANPA FILTER STATUS - untuk testing
|
|
||||||
->where('products.status', 'aktif')
|
|
||||||
->select(
|
|
||||||
'products.id',
|
|
||||||
'products.name',
|
|
||||||
'products.image',
|
|
||||||
'products.price',
|
|
||||||
'products.stock',
|
|
||||||
DB::raw('SUM(transaksi_details.quantity) as total_sold'),
|
|
||||||
DB::raw('COUNT(DISTINCT transaksis.id) as total_orders')
|
|
||||||
)
|
|
||||||
->groupBy(
|
|
||||||
'products.id',
|
|
||||||
'products.name',
|
|
||||||
'products.image',
|
|
||||||
'products.price',
|
|
||||||
'products.stock'
|
|
||||||
)
|
|
||||||
->orderByDesc('total_sold')
|
|
||||||
->take(10)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
Log::info("🔄 Produk dari semua status: " . $bestSellingProducts->count());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hitung statistik
|
// Hitung statistik
|
||||||
$statistics = [
|
$statistics = [
|
||||||
'total_products_sold' => DB::table('transaksi_details')
|
'total_products_sold' => DB::table('transaksi_details')->sum('quantity'),
|
||||||
->join('transaksis', 'transaksi_details.transaksi_id', '=', 'transaksis.id')
|
|
||||||
->where('transaksis.order_status', 'delivered')
|
|
||||||
->sum('transaksi_details.quantity'),
|
|
||||||
|
|
||||||
'total_completed_orders' => Transaksi::where('order_status', 'delivered')->count(),
|
'total_completed_orders' => Transaksi::where('order_status', 'delivered')->count(),
|
||||||
|
|
||||||
'most_popular_product' => $bestSellingProducts->first()
|
'most_popular_product' => $bestSellingProducts->first()
|
||||||
];
|
];
|
||||||
|
|
||||||
// Debug info
|
return view('products.best-selling', compact('bestSellingProducts', 'statistics'));
|
||||||
$debugInfo = [
|
|
||||||
'total_transaksi' => $totalTransaksi,
|
|
||||||
'total_delivered' => $totalDelivered,
|
|
||||||
'total_details' => $totalDetails,
|
|
||||||
'products_found' => $bestSellingProducts->count()
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('products.best-selling', compact('bestSellingProducts', 'statistics', 'debugInfo'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method untuk update status transaksi ke delivered (untuk testing)
|
|
||||||
*/
|
|
||||||
public function updateToDelivered(Request $request)
|
public function updateToDelivered(Request $request)
|
||||||
{
|
{
|
||||||
$transaksiIds = $request->input('ids', []);
|
$transaksiIds = $request->input('ids', []);
|
||||||
|
|
||||||
if (empty($transaksiIds)) {
|
if (empty($transaksiIds)) {
|
||||||
// Update semua transaksi yang ada detail
|
|
||||||
$transaksiIds = TransaksiDetail::distinct()
|
$transaksiIds = TransaksiDetail::distinct()
|
||||||
->pluck('transaksi_id')
|
->pluck('transaksi_id')
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
@ -136,9 +67,6 @@ public function updateToDelivered(Request $request)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method untuk cek status sistem
|
|
||||||
*/
|
|
||||||
public function checkStatus()
|
public function checkStatus()
|
||||||
{
|
{
|
||||||
$status = [
|
$status = [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Transaksi;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class ProfileController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Halaman profil pengguna
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return view('profile.index', compact('user'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update profil pengguna
|
||||||
|
*/
|
||||||
|
public function update(Request $request)
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => ['required', 'string', 'min:8', 'regex:/^[a-zA-Z\s]+$/'],
|
||||||
|
'phone' => ['required', 'string', 'min:11', 'regex:/^(\+62|08)[0-9]+$/'],
|
||||||
|
'address' => ['required', 'string', 'min:12'],
|
||||||
|
], [
|
||||||
|
'name.required' => 'Nama lengkap wajib diisi.',
|
||||||
|
'name.min' => 'Nama lengkap minimal 8 karakter.',
|
||||||
|
'name.regex' => 'Nama hanya boleh menggunakan huruf dan spasi.',
|
||||||
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
|
'phone.min' => 'Nomor telepon minimal 11 karakter.',
|
||||||
|
'phone.regex' => 'Nomor telepon harus diawali +62 atau 08 dan hanya boleh berisi angka.',
|
||||||
|
'address.required' => 'Alamat wajib diisi.',
|
||||||
|
'address.min' => 'Alamat minimal 12 karakter.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'phone' => $request->phone,
|
||||||
|
'address' => $request->address,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('success', 'Profil berhasil diperbarui.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update password pengguna
|
||||||
|
*/
|
||||||
|
public function updatePassword(Request $request)
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'current_password' => ['required'],
|
||||||
|
'password' => ['required', 'confirmed', 'min:8', 'regex:/^(?=.*[a-zA-Z])(?=.*[0-9])\S+$/'],
|
||||||
|
], [
|
||||||
|
'current_password.required' => 'Password lama wajib diisi.',
|
||||||
|
'password.required' => 'Password baru wajib diisi.',
|
||||||
|
'password.min' => 'Password baru minimal 8 karakter.',
|
||||||
|
'password.confirmed' => 'Konfirmasi password tidak cocok.',
|
||||||
|
'password.regex' => 'Password harus terdiri dari huruf dan angka, tanpa spasi.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!Hash::check($request->current_password, $user->password)) {
|
||||||
|
return back()->withErrors([
|
||||||
|
'current_password' => 'Password lama yang kamu masukkan tidak sesuai.'
|
||||||
|
])->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->update(['password' => Hash::make($request->password)]);
|
||||||
|
|
||||||
|
return back()->with('success', 'Password berhasil diperbarui.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Halaman daftar pesanan pengguna
|
||||||
|
*/
|
||||||
|
public function orders(Request $request)
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$status = $request->get('status', 'all');
|
||||||
|
|
||||||
|
$query = Transaksi::with(['details.product', 'cancellation']) // ← tambah cancellation
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
if ($status !== 'all') {
|
||||||
|
$query->where('order_status', $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$orders = $query->paginate(10);
|
||||||
|
|
||||||
|
$totalOrders = Transaksi::where('user_id', $user->id)->count();
|
||||||
|
$pendingOrders = Transaksi::where('user_id', $user->id)->where('order_status', 'pending')->count();
|
||||||
|
$processOrders = Transaksi::where('user_id', $user->id)->where('order_status', 'processing')->count();
|
||||||
|
$shippedOrders = Transaksi::where('user_id', $user->id)->where('order_status', 'shipped')->count();
|
||||||
|
$doneOrders = Transaksi::where('user_id', $user->id)->where('order_status', 'delivered')->count();
|
||||||
|
$cancelOrders = Transaksi::where('user_id', $user->id)->where('order_status', 'cancelled')->count();
|
||||||
|
|
||||||
|
return view('profile.orders', compact(
|
||||||
|
'orders', 'status',
|
||||||
|
'totalOrders', 'pendingOrders', 'processOrders',
|
||||||
|
'shippedOrders', 'doneOrders', 'cancelOrders'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detail pesanan — dengan relasi cancellation
|
||||||
|
*/
|
||||||
|
public function orderDetail($id)
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$order = Transaksi::with(['details.product', 'cancellation']) // ← tambah cancellation
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
return view('profile.order-detail', compact('order'));
|
||||||
|
}
|
||||||
|
// Tambahkan di ProfileController.php — setelah method orderDetail()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tampilkan form batalkan pesanan
|
||||||
|
*/
|
||||||
|
public function cancelForm($id)
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$transaksi = Transaksi::with(['details.product', 'cancellation'])
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
if (!$transaksi->canBeCancelled()) {
|
||||||
|
return redirect()->route('orders.detail', $id)
|
||||||
|
->with('error', 'Pesanan ini tidak dapat dibatalkan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$reasons = \App\Models\OrderCancellation::reasons();
|
||||||
|
|
||||||
|
return view('profile.cancel', compact('transaksi', 'reasons'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simpan pengajuan pembatalan
|
||||||
|
*/
|
||||||
|
public function cancelStore(Request $request, $id)
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$transaksi = Transaksi::with('cancellation')
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
if (!$transaksi->canBeCancelled()) {
|
||||||
|
return redirect()->route('orders.detail', $id)
|
||||||
|
->with('error', 'Pesanan ini tidak dapat dibatalkan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'reason' => ['required', 'string'],
|
||||||
|
'description' => ['nullable', 'string', 'max:500'],
|
||||||
|
], [
|
||||||
|
'reason.required' => 'Pilih alasan pembatalan terlebih dahulu.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ── COD + pending → langsung cancel tanpa persetujuan admin ──
|
||||||
|
if ($transaksi->isAutoCancel()) {
|
||||||
|
$transaksi->update(['order_status' => 'cancelled']);
|
||||||
|
|
||||||
|
// Simpan catatan pembatalan (opsional, untuk riwayat)
|
||||||
|
\App\Models\OrderCancellation::create([
|
||||||
|
'transaksi_id' => $transaksi->id,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'reason' => $request->reason,
|
||||||
|
'description' => $request->description,
|
||||||
|
'status' => 'approved', // langsung approved
|
||||||
|
'admin_note' => 'Dibatalkan otomatis (COD, belum diproses)',
|
||||||
|
'reviewed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('orders.my')
|
||||||
|
->with('success', 'Pesanan berhasil dibatalkan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Selain itu → kirim pengajuan ke admin ──
|
||||||
|
\App\Models\OrderCancellation::create([
|
||||||
|
'transaksi_id' => $transaksi->id,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'reason' => $request->reason,
|
||||||
|
'description' => $request->description,
|
||||||
|
'status' => 'pending',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('orders.detail', $id)
|
||||||
|
->with('success', 'Pengajuan pembatalan berhasil dikirim. Tunggu konfirmasi admin.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,14 @@
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Transaksi;
|
use App\Models\Transaksi;
|
||||||
|
use App\Models\OrderCancellation;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use App\Mail\OrderShippedNotification;
|
||||||
|
use App\Notifications\OrderStatusChanged;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class TransaksiController extends Controller
|
class TransaksiController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -92,107 +97,127 @@ public function edit($id)
|
||||||
* Update the specified transaction in storage.
|
* Update the specified transaction in storage.
|
||||||
* UPDATED: Menambahkan validasi dan update untuk customer info dan shipping
|
* UPDATED: Menambahkan validasi dan update untuk customer info dan shipping
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'customer_name' => 'required|string|max:255',
|
'order_status' => 'required|in:pending,processing,shipped,delivered,cancelled',
|
||||||
'customer_phone' => 'required|string|max:20',
|
'payment_status' => 'required|in:pending,paid,failed',
|
||||||
'customer_email' => 'nullable|email|max:255',
|
'tracking_number'=> 'nullable|string|max:100',
|
||||||
'shipping_address' => 'required|string',
|
'notes' => 'nullable|string',
|
||||||
'city' => 'required|string|max:100',
|
]);
|
||||||
'province' => 'required|string|max:100',
|
|
||||||
'postal_code' => 'required|string|max:10',
|
|
||||||
'order_status' => 'required|in:pending,processing,shipped,delivered,cancelled',
|
|
||||||
'payment_status' => 'required|in:pending,paid,failed',
|
|
||||||
'tracking_number' => 'nullable|string|max:100',
|
|
||||||
'notes' => 'nullable|string'
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$transaksi = Transaksi::findOrFail($id);
|
$transaksi = Transaksi::findOrFail($id);
|
||||||
|
|
||||||
$updateData = [
|
$oldStatus = $transaksi->order_status;
|
||||||
'customer_name' => $request->customer_name,
|
|
||||||
'customer_phone' => $request->customer_phone,
|
|
||||||
'customer_email' => $request->customer_email,
|
|
||||||
'shipping_address' => $request->shipping_address,
|
|
||||||
'city' => $request->city,
|
|
||||||
'province' => $request->province,
|
|
||||||
'postal_code' => $request->postal_code,
|
|
||||||
'order_status' => $request->order_status,
|
|
||||||
'payment_status' => $request->payment_status,
|
|
||||||
'tracking_number' => $request->tracking_number,
|
|
||||||
'notes' => $request->notes
|
|
||||||
];
|
|
||||||
|
|
||||||
// If payment status changed to paid, set paid_at
|
$updateData = [
|
||||||
if ($request->payment_status === 'paid' && $transaksi->payment_status !== 'paid') {
|
'order_status' => $request->order_status,
|
||||||
$updateData['paid_at'] = now();
|
'payment_status' => $request->payment_status,
|
||||||
}
|
'tracking_number' => $request->tracking_number,
|
||||||
|
'notes' => $request->notes,
|
||||||
|
|
||||||
$transaksi->update($updateData);
|
// Field customer & shipping TIDAK diupdate
|
||||||
|
'customer_name' => $transaksi->customer_name,
|
||||||
|
'customer_phone' => $transaksi->customer_phone,
|
||||||
|
'customer_email' => $transaksi->customer_email,
|
||||||
|
'shipping_address'=> $transaksi->shipping_address,
|
||||||
|
'city' => $transaksi->city,
|
||||||
|
'province' => $transaksi->province,
|
||||||
|
'postal_code' => $transaksi->postal_code,
|
||||||
|
];
|
||||||
|
|
||||||
return redirect()
|
if ($request->payment_status === 'paid' && $transaksi->payment_status !== 'paid') {
|
||||||
->route('admin.transaksis.show', $id)
|
$updateData['paid_at'] = now();
|
||||||
->with('success', 'Transaksi berhasil diupdate!');
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return redirect()
|
|
||||||
->back()
|
|
||||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage())
|
|
||||||
->withInput();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
$transaksi->update($updateData);
|
||||||
|
|
||||||
|
// Kirim notifikasi jika order_status berubah
|
||||||
|
if ($transaksi->user_id && $request->order_status !== $oldStatus) {
|
||||||
|
try {
|
||||||
|
$transaksi->user->notify(new OrderStatusChanged($transaksi, $request->order_status));
|
||||||
|
} catch (\Exception $notifError) {
|
||||||
|
Log::warning('Gagal simpan notifikasi: ' . $notifError->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('admin.transaksis.show', $id)
|
||||||
|
->with('success', 'Transaksi berhasil diupdate!');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', 'Terjadi kesalahan: ' . $e->getMessage())
|
||||||
|
->withInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Update order status via AJAX or regular request.
|
* Update order status via AJAX or regular request.
|
||||||
* UPDATED: Menerima field 'order_status' selain 'status'
|
* UPDATED: Menerima field 'order_status' selain 'status'
|
||||||
*/
|
*/
|
||||||
public function updateStatus(Request $request, $id)
|
public function updateStatus(Request $request, $id)
|
||||||
{
|
{
|
||||||
// Accept both 'status' and 'order_status' field names
|
try {
|
||||||
$statusField = $request->has('order_status') ? 'order_status' : 'status';
|
$transaksi = Transaksi::with('details')->findOrFail($id);
|
||||||
|
|
||||||
$request->validate([
|
// Handle order_status
|
||||||
$statusField => 'required|in:pending,processing,shipped,delivered,cancelled',
|
if ($request->has('order_status')) {
|
||||||
]);
|
$request->validate([
|
||||||
|
'order_status' => 'required|in:pending,processing,shipped,delivered,cancelled',
|
||||||
|
]);
|
||||||
|
|
||||||
try {
|
$newStatus = $request->order_status;
|
||||||
$transaksi = Transaksi::findOrFail($id);
|
$oldStatus = $transaksi->order_status;
|
||||||
$newStatus = $request->input($statusField);
|
|
||||||
|
|
||||||
// Use model method if exists, otherwise direct update
|
// Jika sudah cancelled, tidak bisa diubah
|
||||||
if (method_exists($transaksi, 'updateOrderStatus')) {
|
if (in_array($oldStatus, ['cancelled', 'delivered'])) {
|
||||||
$transaksi->updateOrderStatus($newStatus);
|
return response()->json([
|
||||||
} else {
|
'success' => false,
|
||||||
$transaksi->update(['order_status' => $newStatus]);
|
'message' => 'Pesanan yang sudah selesai atau dibatalkan tidak dapat diubah kembali.'
|
||||||
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->ajax()) {
|
$transaksi->update(['order_status' => $newStatus]);
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
if ($newStatus === 'shipped' && $oldStatus !== 'shipped') {
|
||||||
'message' => 'Status berhasil diupdate!'
|
try {
|
||||||
]);
|
Mail::to($transaksi->customer_email)
|
||||||
|
->send(new OrderShippedNotification($transaksi));
|
||||||
|
} catch (\Exception $mailError) {
|
||||||
|
Log::warning('Gagal kirim email shipped: ' . $mailError->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()
|
if ($transaksi->user_id) {
|
||||||
->back()
|
try {
|
||||||
->with('success', 'Status berhasil diupdate!');
|
$transaksi->user->notify(new OrderStatusChanged($transaksi, $newStatus));
|
||||||
|
} catch (\Exception $notifError) {
|
||||||
} catch (\Exception $e) {
|
Log::warning('Gagal simpan notifikasi: ' . $notifError->getMessage());
|
||||||
if ($request->ajax()) {
|
}
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Gagal update status: ' . $e->getMessage()
|
|
||||||
], 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()
|
|
||||||
->back()
|
|
||||||
->with('error', 'Gagal update status: ' . $e->getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// Handle payment_status
|
||||||
|
if ($request->has('payment_status')) {
|
||||||
|
$request->validate([
|
||||||
|
'payment_status' => 'required|in:pending,paid,failed',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$updateData = ['payment_status' => $request->payment_status];
|
||||||
|
if ($request->payment_status === 'paid' && $transaksi->payment_status !== 'paid') {
|
||||||
|
$updateData['paid_at'] = now();
|
||||||
|
}
|
||||||
|
$transaksi->update($updateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['success' => true, 'message' => 'Status berhasil diupdate!']);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Update payment status.
|
* Update payment status.
|
||||||
*/
|
*/
|
||||||
|
|
@ -409,4 +434,72 @@ public function printInvoice($id)
|
||||||
$transaksi = Transaksi::with(['details.product', 'user'])->findOrFail($id);
|
$transaksi = Transaksi::with(['details.product', 'user'])->findOrFail($id);
|
||||||
return view('admin.transaksis.print', compact('transaksi'));
|
return view('admin.transaksis.print', compact('transaksi'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ← tambah di bagian use atas
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Daftar pengajuan pembatalan (Admin)
|
||||||
|
*/
|
||||||
|
public function cancellations()
|
||||||
|
{
|
||||||
|
$cancellations = OrderCancellation::with(['transaksi', 'user'])
|
||||||
|
->orderByRaw("FIELD(status, 'pending', 'approved', 'rejected')")
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->paginate(15);
|
||||||
|
|
||||||
|
return view('admin.cancellations', compact('cancellations'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setujui pengajuan pembatalan
|
||||||
|
*/
|
||||||
|
public function approveCancellation(Request $request, $id)
|
||||||
|
{
|
||||||
|
$cancellation = OrderCancellation::with('transaksi')->findOrFail($id);
|
||||||
|
|
||||||
|
$cancellation->update([
|
||||||
|
'status' => 'approved',
|
||||||
|
'admin_note' => $request->admin_note ?? 'Pengajuan pembatalan disetujui oleh admin.',
|
||||||
|
'reviewed_by' => auth()->id(),
|
||||||
|
'reviewed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Kembalikan stok produk
|
||||||
|
foreach ($cancellation->transaksi->details as $detail) {
|
||||||
|
$product = Product::find($detail->product_id);
|
||||||
|
if ($product) {
|
||||||
|
$product->increment('stock', $detail->quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status pesanan jadi cancelled
|
||||||
|
$cancellation->transaksi->update(['order_status' => 'cancelled']);
|
||||||
|
|
||||||
|
return back()->with('success', 'Pengajuan disetujui. Pesanan dibatalkan & stok dikembalikan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tolak pengajuan pembatalan
|
||||||
|
*/
|
||||||
|
public function rejectCancellation(Request $request, $id)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'admin_note' => ['required', 'string', 'max:500'],
|
||||||
|
], [
|
||||||
|
'admin_note.required' => 'Alasan penolakan wajib diisi.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cancellation = OrderCancellation::findOrFail($id);
|
||||||
|
|
||||||
|
$cancellation->update([
|
||||||
|
'status' => 'rejected',
|
||||||
|
'admin_note' => $request->admin_note,
|
||||||
|
'reviewed_by' => auth()->id(),
|
||||||
|
'reviewed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('success', 'Pengajuan pembatalan berhasil ditolak.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -14,52 +14,21 @@ class UserController extends Controller
|
||||||
/**
|
/**
|
||||||
* Display a listing of the users.
|
* Display a listing of the users.
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
// Ambil semua users dari database dengan pagination
|
|
||||||
$users = User::orderBy('id', 'desc')->paginate(15);
|
|
||||||
|
|
||||||
// Hitung statistik
|
$users = User::orderBy('id', 'desc')->paginate(15);
|
||||||
$totalUsers = User::count();
|
|
||||||
$totalAdmins = User::where('is_admin', 1)->count();
|
|
||||||
$verifiedUsers = User::whereNotNull('email_verified_at')->count();
|
|
||||||
$activeUsers = User::where('created_at', '>=', now()->subDays(30))->count();
|
|
||||||
|
|
||||||
// Debug: Log jumlah users
|
$totalUsers = User::count();
|
||||||
\Log::info('Total users in database: ' . $totalUsers);
|
$totalAdmins = User::where('is_admin', 1)->count();
|
||||||
\Log::info('Users retrieved: ' . $users->count());
|
$verifiedUsers = User::whereNotNull('email_verified_at')->count();
|
||||||
|
$activeUsers = User::where('created_at', '>=', now()->subDays(30))->count();
|
||||||
|
// dd($users->count(), $totalUsers);
|
||||||
|
|
||||||
// Data yang akan dikirim ke view
|
return view('admin.users', compact(
|
||||||
$data = [
|
'users', 'totalUsers', 'totalAdmins', 'verifiedUsers', 'activeUsers'
|
||||||
'users' => $users,
|
));
|
||||||
'totalUsers' => $totalUsers,
|
}
|
||||||
'totalAdmins' => $totalAdmins,
|
|
||||||
'verifiedUsers' => $verifiedUsers,
|
|
||||||
'activeUsers' => $activeUsers
|
|
||||||
];
|
|
||||||
|
|
||||||
// Coba cari view di lokasi yang berbeda
|
|
||||||
if (view()->exists('admin.users.index')) {
|
|
||||||
return view('admin.users.index', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('admin.users', $data);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error('Error in UserController@index: ' . $e->getMessage());
|
|
||||||
|
|
||||||
// Jika error, kirim data kosong
|
|
||||||
return view('admin.users', [
|
|
||||||
'users' => collect()->paginate(15),
|
|
||||||
'totalUsers' => 0,
|
|
||||||
'totalAdmins' => 0,
|
|
||||||
'verifiedUsers' => 0,
|
|
||||||
'activeUsers' => 0,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for creating a new user.
|
* Show the form for creating a new user.
|
||||||
|
|
@ -72,29 +41,61 @@ public function create()
|
||||||
/**
|
/**
|
||||||
* Store a newly created user in database.
|
* Store a newly created user in database.
|
||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$request->validate([
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => [
|
||||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
'required', 'string', 'min:8',
|
||||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
'regex:/^[a-zA-Z\s]+$/' // hanya huruf dan spasi
|
||||||
'phone' => ['nullable', 'string', 'max:20'],
|
],
|
||||||
'address' => ['nullable', 'string'],
|
'email' => [
|
||||||
'is_admin' => ['boolean']
|
'required', 'string', 'min:8', 'email', 'max:255', 'unique:users',
|
||||||
]);
|
'regex:/^[^\s]+@gmail\.com$/' // tidak boleh spasi, harus @gmail.com
|
||||||
|
],
|
||||||
|
'password' => [
|
||||||
|
'required', 'confirmed', 'min:8',
|
||||||
|
'regex:/^(?=.*[a-zA-Z])(?=.*[0-9])\S+$/' // huruf+angka, no spasi
|
||||||
|
],
|
||||||
|
'phone' => [
|
||||||
|
'required', 'string', 'min:12',
|
||||||
|
'regex:/^(\+62)[0-9]+$/' // harus diawali +62, hanya angka
|
||||||
|
],
|
||||||
|
'address' => ['required', 'string', 'min:10'],
|
||||||
|
], [
|
||||||
|
'name.required' => 'Nama lengkap wajib diisi.',
|
||||||
|
'name.min' => 'Nama minimal 8 karakter.',
|
||||||
|
'name.regex' => 'Nama hanya boleh menggunakan huruf dan spasi.',
|
||||||
|
|
||||||
$user = User::create([
|
'email.required' => 'Email wajib diisi.',
|
||||||
'name' => $validated['name'],
|
'email.min' => 'Email minimal 8 karakter.',
|
||||||
'email' => $validated['email'],
|
'email.unique' => 'Email sudah digunakan.',
|
||||||
'password' => Hash::make($validated['password']),
|
'email.regex' => 'Email tidak boleh mengandung spasi dan harus menggunakan @gmail.com.',
|
||||||
'phone' => $validated['phone'] ?? null,
|
|
||||||
'address' => $validated['address'] ?? null,
|
|
||||||
'is_admin' => $request->has('is_admin') ? 1 : 0,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return redirect()->route('admin.users.index')
|
'password.required' => 'Password wajib diisi.',
|
||||||
->with('success', 'Pengguna berhasil ditambahkan');
|
'password.min' => 'Password minimal 8 karakter.',
|
||||||
}
|
'password.confirmed'=> 'Konfirmasi password tidak cocok.',
|
||||||
|
'password.regex' => 'Password tidak boleh mengandung spasi dan harus terdiri dari huruf dan angka.',
|
||||||
|
|
||||||
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
|
'phone.min' => 'Nomor telepon minimal 12 karakter.',
|
||||||
|
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya boleh berisi angka.',
|
||||||
|
|
||||||
|
'address.required' => 'Alamat wajib diisi.',
|
||||||
|
'address.min' => 'Alamat minimal 10 karakter.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
User::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'phone' => $request->phone,
|
||||||
|
'address' => $request->address,
|
||||||
|
'is_admin' => $request->has('is_admin') ? 1 : 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('admin.users')
|
||||||
|
->with('success', 'Pengguna berhasil ditambahkan');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the specified user.
|
* Display the specified user.
|
||||||
|
|
@ -110,60 +111,107 @@ public function show(User $user)
|
||||||
* Show the form for editing the specified user.
|
* Show the form for editing the specified user.
|
||||||
*/
|
*/
|
||||||
public function edit(User $user)
|
public function edit(User $user)
|
||||||
{
|
{
|
||||||
return view('admin.users.edit', [
|
// Hanya boleh edit akun sendiri
|
||||||
'user' => $user
|
if ($user->id !== auth()->id()) {
|
||||||
]);
|
return redirect()->route('admin.users')
|
||||||
|
->with('error', 'Anda tidak dapat mengedit akun pengguna lain.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return view('admin.users.edit', compact('user'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the specified user in database.
|
* Update the specified user in database.
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, User $user)
|
public function update(Request $request, User $user)
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
|
||||||
'name' => ['required', 'string', 'max:255'],
|
|
||||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:users,email,'.$user->id],
|
|
||||||
'phone' => ['nullable', 'string', 'max:20'],
|
|
||||||
'address' => ['nullable', 'string'],
|
|
||||||
'is_admin' => ['boolean']
|
|
||||||
]);
|
|
||||||
|
|
||||||
$updateData = [
|
// Hanya boleh update akun sendiri
|
||||||
'name' => $validated['name'],
|
if ($user->id !== auth()->id()) {
|
||||||
'email' => $validated['email'],
|
return redirect()->route('admin.users')
|
||||||
'phone' => $validated['phone'] ?? null,
|
->with('error', 'Anda tidak dapat mengedit akun pengguna lain.');
|
||||||
'address' => $validated['address'] ?? null,
|
|
||||||
'is_admin' => $request->has('is_admin') ? 1 : 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Update password jika diisi
|
|
||||||
if ($request->filled('password')) {
|
|
||||||
$request->validate([
|
|
||||||
'password' => ['confirmed', Rules\Password::defaults()]
|
|
||||||
]);
|
|
||||||
$updateData['password'] = Hash::make($request->password);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->update($updateData);
|
|
||||||
|
|
||||||
return redirect()->route('admin.users.index')
|
|
||||||
->with('success', 'Pengguna berhasil diperbarui');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => [
|
||||||
|
'required', 'string', 'min:8',
|
||||||
|
'regex:/^[a-zA-Z\s]+$/'
|
||||||
|
],
|
||||||
|
'email' => [
|
||||||
|
'required', 'string', 'min:8', 'max:255',
|
||||||
|
'unique:users,email,' . $user->id,
|
||||||
|
'regex:/^[^\s]+@gmail\.com$/'
|
||||||
|
],
|
||||||
|
'phone' => [
|
||||||
|
'required', 'string', 'min:12',
|
||||||
|
'regex:/^(\+62)[0-9]+$/'
|
||||||
|
],
|
||||||
|
'address' => ['required', 'string', 'min:10'],
|
||||||
|
], [
|
||||||
|
'name.required' => 'Nama lengkap wajib diisi.',
|
||||||
|
'name.min' => 'Nama minimal 8 karakter.',
|
||||||
|
'name.regex' => 'Nama hanya boleh menggunakan huruf dan spasi.',
|
||||||
|
|
||||||
|
'email.required' => 'Email wajib diisi.',
|
||||||
|
'email.min' => 'Email minimal 8 karakter.',
|
||||||
|
'email.unique' => 'Email sudah digunakan.',
|
||||||
|
'email.regex' => 'Email tidak boleh mengandung spasi dan harus menggunakan @gmail.com.',
|
||||||
|
|
||||||
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
|
'phone.min' => 'Nomor telepon minimal 12 karakter.',
|
||||||
|
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya boleh berisi angka.',
|
||||||
|
|
||||||
|
'address.required' => 'Alamat wajib diisi.',
|
||||||
|
'address.min' => 'Alamat minimal 10 karakter.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'phone' => $request->phone,
|
||||||
|
'address' => $request->address,
|
||||||
|
'is_admin' => $request->has('is_admin') ? 1 : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->filled('password')) {
|
||||||
|
$request->validate([
|
||||||
|
'password' => [
|
||||||
|
'confirmed', 'min:8',
|
||||||
|
'regex:/^(?=.*[a-zA-Z])(?=.*[0-9])\S+$/'
|
||||||
|
],
|
||||||
|
], [
|
||||||
|
'password.min' => 'Password minimal 8 karakter.',
|
||||||
|
'password.confirmed' => 'Konfirmasi password tidak cocok.',
|
||||||
|
'password.regex' => 'Password tidak boleh mengandung spasi dan harus terdiri dari huruf dan angka.',
|
||||||
|
]);
|
||||||
|
$data['password'] = Hash::make($request->password);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->update($data);
|
||||||
|
|
||||||
|
return redirect()->route('admin.users')
|
||||||
|
->with('success', 'Pengguna berhasil diperbarui');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the specified user from database.
|
* Remove the specified user from database.
|
||||||
*/
|
*/
|
||||||
public function destroy(User $user)
|
public function destroy(User $user)
|
||||||
{
|
{
|
||||||
// Prevent deleting yourself
|
// Tidak bisa hapus akun siapapun selain diri sendiri
|
||||||
if (auth()->id() === $user->id) {
|
if (auth()->id() !== $user->id) {
|
||||||
return back()->with('error', 'Anda tidak dapat menghapus akun sendiri');
|
return back()->with('error', 'Anda tidak dapat menghapus akun pengguna lain.');
|
||||||
}
|
|
||||||
|
|
||||||
$user->delete();
|
|
||||||
|
|
||||||
return redirect()->route('admin.users.index')
|
|
||||||
->with('success', 'Pengguna berhasil dihapus');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tidak bisa hapus akun sendiri
|
||||||
|
if (auth()->id() === $user->id) {
|
||||||
|
return back()->with('error', 'Anda tidak dapat menghapus akun sendiri.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
|
||||||
|
return redirect()->route('admin.users')
|
||||||
|
->with('success', 'Pengguna berhasil dihapus');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class IsAdmin
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
if (!auth()->check() || !auth()->user()->is_admin) {
|
||||||
|
abort(403, 'Akses ditolak. Anda bukan admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\Transaksi;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class AdminOrderNotification extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public Transaksi $transaksi;
|
||||||
|
|
||||||
|
public function __construct(Transaksi $transaksi)
|
||||||
|
{
|
||||||
|
$this->transaksi = $transaksi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: '🛒 Pesanan Baru #' . $this->transaksi->invoice_number,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'emails.admin-order-notification',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ContactMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public array $data;
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
replyTo: [$this->data['email']],
|
||||||
|
subject: '[Contact Us] ' . $this->data['subject'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'emails.contact',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class OrderShippedNotification extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'Order Shipped Notification',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'view.name',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class OrderCancellation extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'transaksi_id',
|
||||||
|
'user_id',
|
||||||
|
'reason',
|
||||||
|
'description',
|
||||||
|
'status',
|
||||||
|
'admin_note',
|
||||||
|
'reviewed_by',
|
||||||
|
'reviewed_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'reviewed_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function reasons(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Ingin mengubah produk / varian' => 'Ingin mengubah produk / varian',
|
||||||
|
'Ingin mengubah alamat pengiriman' => 'Ingin mengubah alamat pengiriman',
|
||||||
|
'Menemukan harga lebih murah' => 'Menemukan harga lebih murah',
|
||||||
|
'Tidak jadi membeli' => 'Tidak jadi membeli',
|
||||||
|
'Proses pengiriman terlalu lama' => 'Proses pengiriman terlalu lama',
|
||||||
|
'Lainnya' => 'Lainnya',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transaksi()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Transaksi::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reviewer()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'reviewed_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPending(): bool { return $this->status === 'pending'; }
|
||||||
|
public function isApproved(): bool { return $this->status === 'approved'; }
|
||||||
|
public function isRejected(): bool { return $this->status === 'rejected'; }
|
||||||
|
}
|
||||||
|
|
@ -90,17 +90,15 @@ public function getLabelBadgeClassAttribute()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Method untuk mendapatkan label berdasarkan stok
|
// NEW: Method untuk mendapatkan label berdasarkan stok
|
||||||
public function updateLabelBasedOnStock()
|
public function updateLabelBasedOnStock()
|
||||||
{
|
{
|
||||||
if ($this->stock <= 0) {
|
if ($this->stock <= 0) {
|
||||||
$this->label = 'habis';
|
$this->label = 'habis';
|
||||||
} elseif ($this->stock > 1000) {
|
} else {
|
||||||
$this->label = 'terlaris';
|
$this->label = 'tersedia';
|
||||||
} else {
|
|
||||||
$this->label = 'tersedia';
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
// Scope untuk produk aktif
|
// Scope untuk produk aktif
|
||||||
public function scopeActive($query)
|
public function scopeActive($query)
|
||||||
|
|
@ -126,4 +124,9 @@ public function scopeByCategory($query, $category)
|
||||||
{
|
{
|
||||||
return $query->where('category', $category);
|
return $query->where('category', $category);
|
||||||
}
|
}
|
||||||
|
// Relasi ke transaksi details untuk menghitung total terjual
|
||||||
|
public function transaksiDetails()
|
||||||
|
{
|
||||||
|
return $this->hasMany(TransaksiDetail::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,13 +32,13 @@ class Transaksi extends Model
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'paid_at' => 'datetime',
|
'paid_at' => 'datetime',
|
||||||
'subtotal' => 'decimal:2',
|
'subtotal' => 'decimal:2',
|
||||||
'shipping_cost' => 'decimal:2',
|
'shipping_cost'=> 'decimal:2',
|
||||||
'total_amount' => 'decimal:2',
|
'total_amount' => 'decimal:2',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Relationships
|
// ── Relationships ────────────────────────────────────────────
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
|
|
@ -49,7 +49,12 @@ public function details()
|
||||||
return $this->hasMany(TransaksiDetail::class);
|
return $this->hasMany(TransaksiDetail::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scopes
|
public function cancellation()
|
||||||
|
{
|
||||||
|
return $this->hasOne(OrderCancellation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Scopes ───────────────────────────────────────────────────
|
||||||
public function scopePending($query)
|
public function scopePending($query)
|
||||||
{
|
{
|
||||||
return $query->where('order_status', 'pending');
|
return $query->where('order_status', 'pending');
|
||||||
|
|
@ -85,27 +90,57 @@ public function scopeUnpaid($query)
|
||||||
return $query->where('payment_status', 'pending');
|
return $query->where('payment_status', 'pending');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods
|
// ── Methods ──────────────────────────────────────────────────
|
||||||
public function canBeCancelled()
|
|
||||||
{
|
/**
|
||||||
return in_array($this->order_status, ['pending', 'processing']);
|
* Cek apakah pesanan bisa dibatalkan.
|
||||||
|
* Syarat: status pending/processing DAN belum ada pengajuan cancel.
|
||||||
|
*/
|
||||||
|
public function canBeCancelled(): bool
|
||||||
|
{
|
||||||
|
$this->loadMissing('cancellation');
|
||||||
|
|
||||||
|
// Sudah shipped/delivered/cancelled → tidak bisa dibatalkan
|
||||||
|
if (in_array($this->order_status, ['shipped', 'delivered', 'cancelled'])) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function markAsPaid()
|
// Sudah ada pengajuan cancel → tidak bisa duplikat
|
||||||
|
if ($this->cancellation !== null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // ← ini yang hilang
|
||||||
|
}
|
||||||
|
public function isAutoCancel(): bool
|
||||||
|
{
|
||||||
|
return $this->payment_method === 'cod'
|
||||||
|
&& $this->order_status === 'pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPendingCancellation(): bool
|
||||||
|
{
|
||||||
|
$this->loadMissing('cancellation');
|
||||||
|
|
||||||
|
return $this->cancellation !== null
|
||||||
|
&& $this->cancellation->status === 'pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markAsPaid(): bool
|
||||||
{
|
{
|
||||||
return $this->update([
|
return $this->update([
|
||||||
'payment_status' => 'paid',
|
'payment_status' => 'paid',
|
||||||
'paid_at' => now()
|
'paid_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateOrderStatus($status)
|
public function updateOrderStatus(string $status): bool
|
||||||
{
|
{
|
||||||
return $this->update(['order_status' => $status]);
|
return $this->update(['order_status' => $status]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accessor
|
// ── Accessors ────────────────────────────────────────────────
|
||||||
public function getPaymentMethodLabelAttribute()
|
public function getPaymentMethodLabelAttribute(): string
|
||||||
{
|
{
|
||||||
return $this->payment_method === 'transfer' ? 'Transfer Bank' : 'COD';
|
return $this->payment_method === 'transfer' ? 'Transfer Bank' : 'COD';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Transaksi;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Notifications\Messages\DatabaseMessage;
|
||||||
|
|
||||||
|
class OrderStatusChanged extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public Transaksi $transaksi;
|
||||||
|
public string $newStatus;
|
||||||
|
|
||||||
|
public function __construct(Transaksi $transaksi, string $newStatus)
|
||||||
|
{
|
||||||
|
$this->transaksi = $transaksi;
|
||||||
|
$this->newStatus = $newStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function via($notifiable): array
|
||||||
|
{
|
||||||
|
return ['database'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toDatabase($notifiable): array
|
||||||
|
{
|
||||||
|
$messages = [
|
||||||
|
'processing' => '🔄 Pesanan Anda sedang diproses',
|
||||||
|
'shipped' => '📦 Pesanan Anda sedang dalam pengiriman',
|
||||||
|
'delivered' => '✅ Pesanan Anda telah diterima',
|
||||||
|
'cancelled' => '❌ Pesanan Anda dibatalkan',
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'invoice' => $this->transaksi->invoice_number,
|
||||||
|
'status' => $this->newStatus,
|
||||||
|
'message' => $messages[$this->newStatus] ?? 'Status pesanan diperbarui',
|
||||||
|
'transaksi_id'=> $this->transaksi->id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,8 +17,8 @@ public function register(): void
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
\Illuminate\Pagination\Paginator::useBootstrapFive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|
use App\Http\Middleware\IsAdmin; // tambahkan ini
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
|
|
@ -10,9 +11,12 @@
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
//
|
// Daftarkan alias middleware di sini
|
||||||
|
$middleware->alias([
|
||||||
|
'is_admin' => IsAdmin::class,
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
//
|
//
|
||||||
})->create();
|
})->create();
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('MAIL_MAILER', 'log'),
|
'default' => env('MAIL_MAILER', 'log'),
|
||||||
|
'admin_email' => env('ADMIN_EMAIL', 'bayualghozali86@gmail.com'), // ← tambahkan di sini
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('transaksis', function (Blueprint $table) {
|
||||||
|
$table->decimal('discount', 15, 2)->default(0)->after('subtotal');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('transaksis', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('order_cancellations', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('transaksi_id')->constrained('transaksis')->onDelete('cascade');
|
||||||
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
|
$table->string('reason');
|
||||||
|
$table->text('description')->nullable();
|
||||||
|
$table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
|
||||||
|
$table->text('admin_note')->nullable();
|
||||||
|
$table->foreignId('reviewed_by')->nullable()->constrained('users')->onDelete('set null');
|
||||||
|
$table->timestamp('reviewed_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('order_cancellations');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('notifications', function (Blueprint $table) {
|
||||||
|
$table->uuid('id')->primary();
|
||||||
|
$table->string('type');
|
||||||
|
$table->morphs('notifiable');
|
||||||
|
$table->text('data');
|
||||||
|
$table->timestamp('read_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('notifications');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||||
|
$table->string('email')->index();
|
||||||
|
$table->string('token');
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('password_reset_tokens');
|
||||||
|
}
|
||||||
|
};
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 935 KiB |
|
|
@ -0,0 +1,325 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Pengajuan Pembatalan - Admin</title>
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: 'Segoe UI', sans-serif; background: #f8f9fa; }
|
||||||
|
.admin-container { display: flex; min-height: 100vh; }
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 280px; background: linear-gradient(180deg, #28a745, #20c997);
|
||||||
|
color: white; min-height: 100vh; position: fixed; top: 0; left: 0; z-index: 1000;
|
||||||
|
}
|
||||||
|
.sidebar-header { padding: 25px 20px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.1); }
|
||||||
|
.sidebar-header h3 { font-size: 1.8rem; font-weight: bold; margin-bottom: 5px; }
|
||||||
|
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; }
|
||||||
|
.sidebar-menu { padding: 20px 0; }
|
||||||
|
.menu-item {
|
||||||
|
display: block; padding: 15px 25px; color: white;
|
||||||
|
text-decoration: none; transition: all 0.3s; border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
.menu-item:hover, .menu-item.active {
|
||||||
|
background: rgba(255,255,255,0.2); color: white;
|
||||||
|
border-left-color: #fff; transform: translateX(5px);
|
||||||
|
}
|
||||||
|
.menu-item i { width: 20px; margin-right: 10px; }
|
||||||
|
|
||||||
|
.main-content { margin-left: 280px; flex: 1; }
|
||||||
|
.admin-header {
|
||||||
|
background: white; padding: 20px 30px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
.content-area { padding: 30px; }
|
||||||
|
|
||||||
|
.badge-status { padding: 4px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; }
|
||||||
|
.badge-pending { background: #fff3cd; color: #856404; }
|
||||||
|
.badge-approved { background: #d4edda; color: #155724; }
|
||||||
|
.badge-rejected { background: #f8d7da; color: #721c24; }
|
||||||
|
|
||||||
|
.card { border: none; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
|
||||||
|
.card-header { background: white; border-bottom: 2px solid #f8f9fa; border-radius: 12px 12px 0 0 !important; }
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
position: fixed; top: 20px; right: 20px; z-index: 1100;
|
||||||
|
background: linear-gradient(45deg, #dc3545, #c82333);
|
||||||
|
color: white; border: none; padding: 12px 20px;
|
||||||
|
border-radius: 25px; font-weight: 600; text-decoration: none; transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.back-button:hover { color: white; transform: translateY(-2px); }
|
||||||
|
|
||||||
|
.table th { background: #f8f9fa; font-weight: 600; color: #495057; font-size: 0.85rem; }
|
||||||
|
.table td { vertical-align: middle; }
|
||||||
|
|
||||||
|
.action-btn { padding: 5px 12px; border-radius: 6px; font-size: 0.8rem; font-weight: 600; border: none; cursor: pointer; }
|
||||||
|
.action-btn.btn-success { background-color: #28a745 !important; color: white !important; opacity: 1 !important; }
|
||||||
|
.action-btn.btn-danger { background-color: #dc3545 !important; color: white !important; opacity: 1 !important; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
{{-- Sidebar --}}
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item">
|
||||||
|
<i class="fas fa-seedling"></i>Kelola Produk
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item">
|
||||||
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item active">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item">
|
||||||
|
<i class="fas fa-users"></i>Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="menu-item">
|
||||||
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
|
</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item">
|
||||||
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
|
</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{-- Main Content --}}
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="admin-header">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb mb-1">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
|
||||||
|
<li class="breadcrumb-item active">Pengajuan Pembatalan</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2>Pengajuan Pembatalan Pesanan</h2>
|
||||||
|
<p class="mb-0">Kelola semua permintaan pembatalan dari pelanggan.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area">
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Stats --}}
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card p-3 border-start border-warning border-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-warning fw-bold small">MENUNGGU REVIEW</div>
|
||||||
|
<div class="fs-3 fw-bold">{{ $cancellations->where('status','pending')->count() }}</div>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-clock fa-2x text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card p-3 border-start border-success border-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-success fw-bold small">DISETUJUI</div>
|
||||||
|
<div class="fs-3 fw-bold">{{ $cancellations->where('status','approved')->count() }}</div>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-check-circle fa-2x text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card p-3 border-start border-danger border-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-danger fw-bold small">DITOLAK</div>
|
||||||
|
<div class="fs-3 fw-bold">{{ $cancellations->where('status','rejected')->count() }}</div>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-times-circle fa-2x text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Tabel --}}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="mb-0 fw-bold text-success">
|
||||||
|
<i class="fas fa-times-circle me-2"></i>
|
||||||
|
Daftar Pengajuan Pembatalan ({{ $cancellations->total() }})
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="ps-3">Invoice</th>
|
||||||
|
<th>Pelanggan</th>
|
||||||
|
<th>Alasan</th>
|
||||||
|
<th>Keterangan</th>
|
||||||
|
<th>Tgl Pengajuan</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($cancellations as $item)
|
||||||
|
<tr>
|
||||||
|
<td class="ps-3">
|
||||||
|
<strong class="d-block">{{ $item->transaksi->invoice_number ?? '-' }}</strong>
|
||||||
|
<small class="text-muted">
|
||||||
|
Rp{{ number_format($item->transaksi->total_amount ?? 0, 0, ',', '.') }}
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>{{ $item->user->name ?? '-' }}</strong><br>
|
||||||
|
<small class="text-muted">{{ $item->user->email ?? '-' }}</small>
|
||||||
|
</td>
|
||||||
|
<td style="max-width:180px;">
|
||||||
|
<small>{{ $item->reason }}</small>
|
||||||
|
</td>
|
||||||
|
<td style="max-width:200px;">
|
||||||
|
<small class="text-muted">{{ $item->description ?? '-' }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small>{{ $item->created_at->format('d M Y') }}</small><br>
|
||||||
|
<small class="text-muted">{{ $item->created_at->format('H:i') }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge-status badge-{{ $item->status }}">
|
||||||
|
{{ $item->status === 'pending' ? 'Menunggu' : ($item->status === 'approved' ? 'Disetujui' : 'Ditolak') }}
|
||||||
|
</span>
|
||||||
|
@if($item->reviewed_at)
|
||||||
|
<br><small class="text-muted">{{ $item->reviewed_at->format('d M Y') }}</small>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
@if($item->status === 'pending')
|
||||||
|
{{-- Tombol Setujui --}}
|
||||||
|
<form action="{{ route('admin.cancellations.approve', $item->id) }}" method="POST"
|
||||||
|
class="d-inline"
|
||||||
|
onsubmit="return confirm('Setujui pembatalan pesanan {{ $item->transaksi->invoice_number ?? '' }}?')">
|
||||||
|
@csrf
|
||||||
|
<textarea name="admin_note" style="display:none"> disetujui</textarea>
|
||||||
|
<button type="submit" class="action-btn btn-success text-white me-1">
|
||||||
|
<i class="fas fa-check me-1"></i>Setujui
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{-- Tombol Tolak --}}
|
||||||
|
<button type="button"
|
||||||
|
class="action-btn btn-danger text-white"
|
||||||
|
onclick="showRejectModal({{ $item->id }}, '{{ $item->transaksi->invoice_number ?? '' }}')">
|
||||||
|
<i class="fas fa-times me-1"></i>Tolak
|
||||||
|
</button>
|
||||||
|
@else
|
||||||
|
@if($item->status === 'approved')
|
||||||
|
<span class="action-btn btn-success">Disetujui</span>
|
||||||
|
@elseif($item->status === 'rejected')
|
||||||
|
<span class="action-btn btn-danger">Ditolak</span>
|
||||||
|
@else
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-5">
|
||||||
|
<i class="fas fa-inbox fa-3x text-muted mb-3 d-block"></i>
|
||||||
|
<p class="text-muted">Belum ada pengajuan pembatalan</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Pagination --}}
|
||||||
|
@if($cancellations->hasPages())
|
||||||
|
<div class="d-flex justify-content-between align-items-center p-3 border-top">
|
||||||
|
<small class="text-muted">
|
||||||
|
Menampilkan {{ $cancellations->firstItem() }} - {{ $cancellations->lastItem() }}
|
||||||
|
dari {{ $cancellations->total() }} pengajuan
|
||||||
|
</small>
|
||||||
|
{{ $cancellations->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Modal Tolak --}}
|
||||||
|
<div class="modal fade" id="rejectModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" style="max-width:420px;">
|
||||||
|
<div class="modal-content border-0" style="border-radius:14px; overflow:hidden;">
|
||||||
|
<div class="modal-header bg-danger text-white">
|
||||||
|
<h6 class="modal-title fw-bold mb-0">
|
||||||
|
<i class="fas fa-times-circle me-2"></i>Tolak Pengajuan
|
||||||
|
</h6>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form id="rejectForm" method="POST">
|
||||||
|
@csrf
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<p class="mb-3">Pesanan: <strong id="rejectInvoice"></strong></p>
|
||||||
|
<label class="form-label fw-semibold">
|
||||||
|
Alasan Penolakan <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<textarea name="admin_note" class="form-control" rows="3"
|
||||||
|
placeholder="Jelaskan alasan penolakan kepada pelanggan..."
|
||||||
|
required maxlength="500"></textarea>
|
||||||
|
<small class="text-muted">Wajib diisi. Maks. 500 karakter.</small>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 pt-0">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-danger fw-bold">
|
||||||
|
<i class="fas fa-times me-1"></i>Tolak Pengajuan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function showRejectModal(id, invoice) {
|
||||||
|
document.getElementById('rejectInvoice').textContent = invoice;
|
||||||
|
document.getElementById('rejectForm').action = `/admin/cancellations/${id}/reject`;
|
||||||
|
new bootstrap.Modal(document.getElementById('rejectModal')).show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -5,217 +5,94 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Dashboard Admin - Bibit Cabai</title>
|
<title>Dashboard Admin - Bibit Cabai</title>
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Font Awesome -->
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
<!-- Custom Styles -->
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
margin: 0;
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f8f9fa; }
|
||||||
padding: 0;
|
.admin-container { display: flex; min-height: 100vh; }
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
/* Sidebar */
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
.sidebar { width: 280px; background: linear-gradient(180deg, #28a745, #20c997); color: white; min-height: 100vh; position: fixed; top: 0; left: 0; z-index: 1000; transition: all 0.3s ease; }
|
||||||
background: #f8f9fa;
|
.sidebar-header { padding: 25px 20px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.1); }
|
||||||
}
|
.sidebar-header h3 { font-size: 1.8rem; font-weight: bold; margin-bottom: 5px; }
|
||||||
|
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; }
|
||||||
.admin-container {
|
.sidebar-menu { padding: 20px 0; }
|
||||||
display: flex;
|
.menu-item { display: block; padding: 15px 25px; color: white; text-decoration: none; transition: all 0.3s; border-left: 4px solid transparent; cursor: pointer; }
|
||||||
min-height: 100vh;
|
.menu-item:hover, .menu-item.active { background: rgba(255,255,255,0.1); color: white; text-decoration: none; border-left-color: #fff; transform: translateX(5px); }
|
||||||
}
|
.menu-item i { width: 20px; margin-right: 10px; }
|
||||||
|
|
||||||
/* Sidebar Styles */
|
|
||||||
.sidebar {
|
|
||||||
width: 280px;
|
|
||||||
background: linear-gradient(180deg, #28a745, #20c997);
|
|
||||||
color: white;
|
|
||||||
min-height: 100vh;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header {
|
|
||||||
padding: 25px 20px;
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header h3 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header p {
|
|
||||||
opacity: 0.8;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-menu {
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item {
|
|
||||||
display: block;
|
|
||||||
padding: 15px 25px;
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.3s;
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item:hover, .menu-item.active {
|
|
||||||
background: rgba(255,255,255,0.1);
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
border-left-color: #fff;
|
|
||||||
transform: translateX(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item i {
|
|
||||||
width: 20px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Content */
|
/* Main Content */
|
||||||
.main-content {
|
.main-content { margin-left: 280px; flex: 1; min-height: 100vh; }
|
||||||
margin-left: 280px;
|
.admin-header { background: white; padding: 20px 30px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-bottom: 1px solid #dee2e6; }
|
||||||
flex: 1;
|
.content-area { padding: 30px; }
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-header {
|
|
||||||
background: white;
|
|
||||||
padding: 20px 30px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
border-bottom: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-area {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stats Cards */
|
/* Stats Cards */
|
||||||
.stats-grid {
|
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 25px; margin-bottom: 40px; }
|
||||||
display: grid;
|
.stat-card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); border-left: 5px solid; transition: transform 0.3s ease; }
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
.stat-card:hover { transform: translateY(-5px); }
|
||||||
gap: 25px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background: white;
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
|
||||||
border-left: 5px solid;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card.products { border-left-color: #28a745; }
|
.stat-card.products { border-left-color: #28a745; }
|
||||||
.stat-card.orders { border-left-color: #007bff; }
|
.stat-card.orders { border-left-color: #007bff; }
|
||||||
.stat-card.revenue { border-left-color: #ffc107; }
|
.stat-card.revenue { border-left-color: #ffc107; }
|
||||||
.stat-card.users { border-left-color: #6f42c1; }
|
.stat-card.users { border-left-color: #6f42c1; }
|
||||||
|
.stat-number { font-size: 2.5rem; font-weight: bold; margin-bottom: 8px; }
|
||||||
.stat-number {
|
.stat-label { color: #6c757d; font-size: 1rem; font-weight: 500; }
|
||||||
font-size: 2.5rem;
|
.stat-icon { float: right; font-size: 2.5rem; opacity: 0.3; margin-top: -10px; }
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
color: #6c757d;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-icon {
|
|
||||||
float: right;
|
|
||||||
font-size: 2.5rem;
|
|
||||||
opacity: 0.3;
|
|
||||||
margin-top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Back Button */
|
/* Back Button */
|
||||||
.back-button {
|
.back-button { position: fixed; top: 20px; right: 20px; z-index: 1100; background: linear-gradient(45deg, #dc3545, #c82333); color: white; border: none; padding: 12px 20px; border-radius: 25px; font-weight: 600; transition: all 0.3s ease; text-decoration: none; }
|
||||||
position: fixed;
|
.back-button:hover { background: linear-gradient(45deg, #c82333, #a71e2a); transform: translateY(-2px); color: white; text-decoration: none; }
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
/* Chart Card Header */
|
||||||
z-index: 1100;
|
.chart-card-header {
|
||||||
background: linear-gradient(45deg, #dc3545, #c82333);
|
display: flex;
|
||||||
color: white;
|
align-items: center;
|
||||||
border: none;
|
justify-content: space-between;
|
||||||
padding: 12px 20px;
|
flex-wrap: wrap;
|
||||||
border-radius: 25px;
|
gap: 10px;
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button:hover {
|
/* Lihat Detail Button */
|
||||||
background: linear-gradient(45deg, #c82333, #a71e2a);
|
.btn-lihat-detail {
|
||||||
transform: translateY(-2px);
|
background: rgba(255,255,255,0.2);
|
||||||
color: white;
|
color: white;
|
||||||
|
border: 2px solid rgba(255,255,255,0.6);
|
||||||
|
padding: 7px 18px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.btn-lihat-detail:hover {
|
||||||
|
background: white;
|
||||||
|
color: #28a745;
|
||||||
|
border-color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Empty State */
|
/* Empty State */
|
||||||
.empty-state {
|
.empty-state { text-align: center; padding: 60px 20px; color: #6c757d; }
|
||||||
text-align: center;
|
.empty-state i { font-size: 4rem; margin-bottom: 20px; opacity: 0.3; }
|
||||||
padding: 60px 20px;
|
.empty-state h4 { font-size: 1.5rem; margin-bottom: 10px; }
|
||||||
color: #6c757d;
|
.empty-state p { font-size: 1rem; margin-bottom: 20px; }
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state i {
|
|
||||||
font-size: 4rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state h4 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state p {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.sidebar {
|
.sidebar { width: 100%; position: relative; min-height: auto; }
|
||||||
width: 100%;
|
.main-content { margin-left: 0; }
|
||||||
position: relative;
|
.stats-grid { grid-template-columns: 1fr; }
|
||||||
min-height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- Back Button -->
|
|
||||||
<a href="{{ route('home') }}" class="back-button">
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -237,16 +114,20 @@
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.orders') }}" class="menu-item {{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
<a href="{{ route('admin.orders') }}" class="menu-item {{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
||||||
<i class="fas fa-shopping-cart"></i>Pesanan
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
{{-- ↓ TAMBAHKAN DI SINI ↓ --}}
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.users') }}" class="menu-item {{ request()->routeIs('admin.users') ? 'active' : '' }}">
|
<a href="{{ route('admin.users') }}" class="menu-item {{ request()->routeIs('admin.users') ? 'active' : '' }}">
|
||||||
<i class="fas fa-users"></i>Pengguna
|
<i class="fas fa-users"></i>Pengguna
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.reports') }}" class="menu-item {{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
<a href="{{ route('admin.laporan') }}" class="menu-item {{ request()->routeIs('admin.laporan') ? 'active' : '' }}">
|
||||||
<i class="fas fa-chart-line"></i>Laporan
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.settings') }}" class="menu-item {{ request()->routeIs('admin.settings') ? 'active' : '' }}">
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item {{ request()->routeIs('admin.settings') ? 'active' : '' }}">
|
||||||
<i class="fas fa-cog"></i>Pengaturan
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
</a>
|
</a> -->
|
||||||
<div class="mt-4 px-3">
|
<div class="mt-4 px-3">
|
||||||
<form method="POST" action="{{ route('admin.logout') }}">
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
@ -268,42 +149,58 @@
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card products">
|
<div class="stat-card products">
|
||||||
<div class="stat-number text-success">{{ $totalProducts ?? 0 }}</div>
|
<div class="stat-number text-success">{{ $stats['total_products'] }}</div>
|
||||||
<div class="stat-label">Total Produk</div>
|
<div class="stat-label">Total Produk</div>
|
||||||
<i class="fas fa-seedling stat-icon text-success"></i>
|
<i class="fas fa-seedling stat-icon text-success"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card orders">
|
<div class="stat-card orders">
|
||||||
<div class="stat-number text-primary">{{ $activeProducts ?? 0 }}</div>
|
<div class="stat-number text-primary">{{ $stats['active_products'] }}</div>
|
||||||
<div class="stat-label">Produk Aktif</div>
|
<div class="stat-label">Produk Aktif</div>
|
||||||
<i class="fas fa-check-circle stat-icon text-primary"></i>
|
<i class="fas fa-check-circle stat-icon text-primary"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card revenue">
|
<div class="stat-card revenue">
|
||||||
<div class="stat-number text-warning">{{ $totalSold ?? 0 }}</div>
|
<div class="stat-number text-warning">{{ $stats['total_sold'] }}</div>
|
||||||
<div class="stat-label">Total Terjual</div>
|
<div class="stat-label">Total Terjual</div>
|
||||||
<i class="fas fa-chart-line stat-icon text-warning"></i>
|
<i class="fas fa-chart-line stat-icon text-warning"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card users">
|
<div class="stat-card users">
|
||||||
<div class="stat-number text-info">{{ $lowStock ?? 0 }}</div>
|
<div class="stat-number text-info">{{ $stats['low_stock'] }}</div>
|
||||||
<div class="stat-label">Stok Menipis</div>
|
<div class="stat-label">Stok Menipis</div>
|
||||||
<i class="fas fa-exclamation-triangle stat-icon text-info"></i>
|
<i class="fas fa-exclamation-triangle stat-icon text-info"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-12 mb-4">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header bg-success text-white">
|
<div class="card-header bg-success text-white">
|
||||||
<h5 class="mb-0">📈 Grafik Penjualan</h5>
|
<div class="chart-card-header">
|
||||||
|
<h5 class="mb-0">📈 Grafik Pendapatan Harian (30 Hari Terakhir)</h5>
|
||||||
|
{{-- Tombol Lihat Detail → mengarah ke halaman laporan --}}
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="btn-lihat-detail">
|
||||||
|
<i class="fas fa-external-link-alt"></i> Lihat Detail
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="empty-state">
|
@if(count($chartLabels) > 0)
|
||||||
<i class="fas fa-chart-line"></i>
|
<canvas id="salesChart" height="80"></canvas>
|
||||||
<h4>Belum Ada Data</h4>
|
@else
|
||||||
<p>Grafik penjualan akan ditampilkan setelah ada transaksi</p>
|
<div class="empty-state">
|
||||||
</div>
|
<i class="fas fa-chart-line"></i>
|
||||||
|
<h4>Belum Ada Data</h4>
|
||||||
|
<p>Grafik pendapatan akan ditampilkan setelah ada transaksi</p>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="btn btn-success">
|
||||||
|
<i class="fas fa-chart-bar me-2"></i>Buka Laporan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="row">
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-md-6 mb-4">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header bg-info text-white">
|
<div class="card-header bg-info text-white">
|
||||||
|
|
@ -321,9 +218,54 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
|
<script>
|
||||||
|
@if(count($chartLabels) > 0)
|
||||||
|
const ctx = document.getElementById('salesChart').getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: {!! json_encode($chartLabels) !!},
|
||||||
|
datasets: [{
|
||||||
|
label: 'Penjualan (Rp)',
|
||||||
|
data: {!! json_encode($chartData) !!},
|
||||||
|
borderColor: '#28a745',
|
||||||
|
backgroundColor: 'rgba(40, 167, 69, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
pointBackgroundColor: '#28a745',
|
||||||
|
pointRadius: 4,
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
return 'Rp ' + Number(context.parsed.y).toLocaleString('id-ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return 'Rp ' + Number(value).toLocaleString('id-ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Lupa Password Admin - TA Bibit Cabai</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.btn-primary-custom {
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.btn-primary-custom:hover {
|
||||||
|
background: linear-gradient(135deg, #0d7377 0%, #2dd654 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(17, 153, 142, 0.3);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.btn-primary-custom:disabled {
|
||||||
|
background: #adb5bd;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #11998e;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(17, 153, 142, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step Indicator */
|
||||||
|
.step-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
.step-circle {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.step-circle.active { background: #11998e; color: #fff; }
|
||||||
|
.step-circle.done { background: #11998e; color: #fff; }
|
||||||
|
.step-circle.idle { background: #dee2e6; color: #888; }
|
||||||
|
.step-label { font-size: 11px; white-space: nowrap; margin-top: 4px; }
|
||||||
|
.step-label.active { color: #11998e; font-weight: 700; }
|
||||||
|
.step-label.idle { color: #aaa; }
|
||||||
|
.step-item { display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.step-line { height: 2px; width: 36px; margin: 0 6px 18px; }
|
||||||
|
.step-line.done { background: #11998e; }
|
||||||
|
.step-line.idle { background: #dee2e6; }
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-4 col-lg-4">
|
||||||
|
<div class="card login-card">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
|
||||||
|
{{-- Icon --}}
|
||||||
|
<div class="icon-box">🔑</div>
|
||||||
|
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<h4 class="fw-bold text-dark mb-1">Lupa Password Admin</h4>
|
||||||
|
<p class="text-muted small">Masukkan email admin yang terdaftar</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Step Indicator --}}
|
||||||
|
<div class="step-wrapper">
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle active">1</div>
|
||||||
|
<div class="step-label active">Email</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line idle"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle idle">2</div>
|
||||||
|
<div class="step-label idle">Kode OTP</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line idle"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle idle">3</div>
|
||||||
|
<div class="step-label idle">Password Baru</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Error --}}
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show py-2" role="alert">
|
||||||
|
<small>
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<div>⚠️ {{ $error }}</div>
|
||||||
|
@endforeach
|
||||||
|
</small>
|
||||||
|
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show py-2" role="alert">
|
||||||
|
<small>⚠️ {{ session('error') }}</small>
|
||||||
|
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Form --}}
|
||||||
|
<form method="POST" action="{{ route('admin.password.email') }}" id="forgotForm" novalidate>
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="email" class="form-label fw-semibold">Email Admin</label>
|
||||||
|
<input type="email"
|
||||||
|
class="form-control @error('email') is-invalid @enderror"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
value="{{ old('email') }}"
|
||||||
|
placeholder="admin@example.com"
|
||||||
|
autofocus
|
||||||
|
required>
|
||||||
|
<div class="form-text text-muted small">
|
||||||
|
ℹ️ Hanya akun admin yang terdaftar di sistem
|
||||||
|
</div>
|
||||||
|
@error('email')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
<div id="emailError" class="text-danger mt-1" style="display:none;">
|
||||||
|
<small>⚠️ Format email tidak valid</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary-custom py-2 fw-bold" id="submitBtn" disabled>
|
||||||
|
📨 Kirim Kode Verifikasi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="{{ route('admin.login') }}" class="text-decoration-none small" style="color:#11998e;">
|
||||||
|
← Kembali ke Login Admin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const emailInput = document.getElementById('email');
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
const emailError = document.getElementById('emailError');
|
||||||
|
|
||||||
|
function validateEmail(val) {
|
||||||
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val) && !/\s/.test(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
emailInput.addEventListener('input', function () {
|
||||||
|
const val = this.value.trim();
|
||||||
|
if (val.length > 0 && !validateEmail(val)) {
|
||||||
|
emailError.style.display = 'block';
|
||||||
|
this.classList.add('is-invalid');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
} else {
|
||||||
|
emailError.style.display = 'none';
|
||||||
|
this.classList.remove('is-invalid');
|
||||||
|
submitBtn.disabled = val.length === 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emailInput.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === ' ') e.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,612 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Laporan Penjualan - Bibit Cabai</title>
|
||||||
|
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f8f9fa; }
|
||||||
|
.admin-container { display: flex; min-height: 100vh; }
|
||||||
|
|
||||||
|
/* ===== SIDEBAR (sama persis dengan dashboard) ===== */
|
||||||
|
.sidebar { width: 280px; background: linear-gradient(180deg, #28a745, #20c997); color: white; min-height: 100vh; position: fixed; top: 0; left: 0; z-index: 1000; transition: all 0.3s ease; }
|
||||||
|
.sidebar-header { padding: 25px 20px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.1); }
|
||||||
|
.sidebar-header h3 { font-size: 1.8rem; font-weight: bold; margin-bottom: 5px; }
|
||||||
|
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; }
|
||||||
|
.sidebar-menu { padding: 20px 0; }
|
||||||
|
.menu-item { display: block; padding: 15px 25px; color: white; text-decoration: none; transition: all 0.3s; border-left: 4px solid transparent; cursor: pointer; }
|
||||||
|
.menu-item:hover, .menu-item.active { background: rgba(255,255,255,0.1); color: white; text-decoration: none; border-left-color: #fff; transform: translateX(5px); }
|
||||||
|
.menu-item i { width: 20px; margin-right: 10px; }
|
||||||
|
|
||||||
|
/* ===== MAIN CONTENT ===== */
|
||||||
|
.main-content { margin-left: 280px; flex: 1; min-height: 100vh; }
|
||||||
|
.admin-header { background: white; padding: 20px 30px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-bottom: 1px solid #dee2e6; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
|
||||||
|
.admin-header-left h2 { margin-bottom: 2px; }
|
||||||
|
.admin-header-left p { margin: 0; color: #6c757d; font-size: 0.9rem; }
|
||||||
|
.content-area { padding: 30px; }
|
||||||
|
|
||||||
|
/* Back Button */
|
||||||
|
.back-button { position: fixed; top: 20px; right: 20px; z-index: 1100; background: linear-gradient(45deg, #dc3545, #c82333); color: white; border: none; padding: 12px 20px; border-radius: 25px; font-weight: 600; transition: all 0.3s ease; text-decoration: none; }
|
||||||
|
.back-button:hover { background: linear-gradient(45deg, #c82333, #a71e2a); transform: translateY(-2px); color: white; text-decoration: none; }
|
||||||
|
|
||||||
|
/* ===== FILTER BAR ===== */
|
||||||
|
.filter-bar {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px 25px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
margin-bottom: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.filter-bar .form-label { font-weight: 600; color: #495057; font-size: 0.85rem; margin-bottom: 6px; }
|
||||||
|
.filter-bar .form-control, .filter-bar .form-select {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1.5px solid #dee2e6;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 9px 13px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
.filter-bar .form-control:focus, .filter-bar .form-select:focus {
|
||||||
|
border-color: #28a745;
|
||||||
|
box-shadow: 0 0 0 3px rgba(40,167,69,0.12);
|
||||||
|
}
|
||||||
|
.btn-filter {
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 22px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.btn-filter:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(40,167,69,0.35); color: white; }
|
||||||
|
.btn-export {
|
||||||
|
background: white;
|
||||||
|
color: #28a745;
|
||||||
|
border: 2px solid #28a745;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.btn-export:hover { background: #28a745; color: white; text-decoration: none; }
|
||||||
|
|
||||||
|
/* ===== RINGKASAN CARDS ===== */
|
||||||
|
.summary-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
.summary-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 22px 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
border-top: 4px solid;
|
||||||
|
transition: transform 0.25s;
|
||||||
|
}
|
||||||
|
.summary-card:hover { transform: translateY(-4px); }
|
||||||
|
.summary-card.pendapatan { border-top-color: #28a745; }
|
||||||
|
.summary-card.transaksi { border-top-color: #007bff; }
|
||||||
|
.summary-card.produk { border-top-color: #fd7e14; }
|
||||||
|
.summary-card.rata { border-top-color: #6f42c1; }
|
||||||
|
.summary-val { font-size: 1.8rem; font-weight: 700; line-height: 1.2; }
|
||||||
|
.summary-val.green { color: #28a745; }
|
||||||
|
.summary-val.blue { color: #007bff; }
|
||||||
|
.summary-val.orange { color: #fd7e14; }
|
||||||
|
.summary-val.purple { color: #6f42c1; }
|
||||||
|
.summary-lbl { color: #6c757d; font-size: 0.88rem; font-weight: 500; margin-top: 4px; }
|
||||||
|
.summary-icon { float: right; font-size: 2rem; opacity: 0.18; margin-top: -6px; }
|
||||||
|
|
||||||
|
/* ===== CHART SECTION ===== */
|
||||||
|
.section-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
margin-bottom: 28px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.section-header {
|
||||||
|
padding: 18px 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.section-header.green { background: linear-gradient(135deg, #28a745, #20c997); }
|
||||||
|
.section-header.blue { background: linear-gradient(135deg, #007bff, #0056b3); }
|
||||||
|
.section-header.orange { background: linear-gradient(135deg, #fd7e14, #e55a00); }
|
||||||
|
.section-body { padding: 22px; }
|
||||||
|
|
||||||
|
/* ===== TABEL ===== */
|
||||||
|
.table-responsive { border-radius: 8px; overflow: hidden; }
|
||||||
|
.table-laporan { font-size: 0.9rem; margin: 0; }
|
||||||
|
.table-laporan thead th {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #495057;
|
||||||
|
font-weight: 700;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
padding: 13px 15px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.table-laporan tbody td { padding: 12px 15px; vertical-align: middle; border-bottom: 1px solid #f0f0f0; }
|
||||||
|
.table-laporan tbody tr:hover { background: #f8fff9; }
|
||||||
|
.table-laporan tbody tr:last-child td { border-bottom: none; }
|
||||||
|
|
||||||
|
/* Badge status */
|
||||||
|
.badge-status { padding: 5px 12px; border-radius: 20px; font-size: 0.78rem; font-weight: 600; }
|
||||||
|
.badge-selesai { background: #d4edda; color: #155724; }
|
||||||
|
.badge-proses { background: #cce5ff; color: #004085; }
|
||||||
|
.badge-batal { background: #f8d7da; color: #721c24; }
|
||||||
|
.badge-kirim { background: #fff3cd; color: #856404; }
|
||||||
|
|
||||||
|
/* Progress bar produk */
|
||||||
|
.progress-thin { height: 6px; border-radius: 10px; background: #e9ecef; }
|
||||||
|
.progress-fill-green { background: linear-gradient(90deg, #28a745, #20c997); }
|
||||||
|
.progress-fill-orange { background: linear-gradient(90deg, #fd7e14, #ffc107); }
|
||||||
|
|
||||||
|
/* Empty state */
|
||||||
|
.empty-state { text-align: center; padding: 50px 20px; color: #adb5bd; }
|
||||||
|
.empty-state i { font-size: 3.5rem; margin-bottom: 15px; display: block; }
|
||||||
|
|
||||||
|
/* Pagination */
|
||||||
|
.page-link { color: #28a745; border-color: #dee2e6; }
|
||||||
|
.page-item.active .page-link { background-color: #28a745; border-color: #28a745; }
|
||||||
|
.page-link:hover { color: #155724; border-color: #28a745; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar { width: 100%; position: relative; min-height: auto; }
|
||||||
|
.main-content { margin-left: 0; }
|
||||||
|
.summary-grid { grid-template-columns: 1fr 1fr; }
|
||||||
|
.filter-bar { flex-direction: column; align-items: stretch; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Back Button -->
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
<!-- ===== SIDEBAR ===== -->
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item {{ request()->routeIs('admin.products.*') ? 'active' : '' }}">
|
||||||
|
<i class="fas fa-seedling"></i>Kelola Produk
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item {{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
||||||
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
{{-- ↓ TAMBAHKAN DI SINI ↓ --}}
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item {{ request()->routeIs('admin.users') ? 'active' : '' }}">
|
||||||
|
<i class="fas fa-users"></i>Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="menu-item {{ request()->routeIs('admin.laporan') ? 'active' : '' }}">
|
||||||
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
|
</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item {{ request()->routeIs('admin.settings') ? 'active' : '' }}">
|
||||||
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
|
</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100"
|
||||||
|
onclick="return confirm('Apakah Anda yakin ingin logout?')">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- ===== MAIN CONTENT ===== -->
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="admin-header">
|
||||||
|
<div class="admin-header-left">
|
||||||
|
<h2>📊 Laporan Penjualan</h2>
|
||||||
|
<p>Detail pendapatan, transaksi, dan produk yang keluar</p>
|
||||||
|
</div>
|
||||||
|
<a href="{{ route('admin.laporan.export') }}" class="btn-export">
|
||||||
|
<i class="fas fa-file-excel"></i> Export Excel
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area">
|
||||||
|
|
||||||
|
{{-- ===== FILTER BAR ===== --}}
|
||||||
|
<div class="filter-bar">
|
||||||
|
<form method="GET" action="{{ route('admin.laporan') }}" style="display:contents">
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Dari Tanggal</label>
|
||||||
|
<input type="date" name="start_date" class="form-control"
|
||||||
|
value="{{ request('start_date', now()->startOfMonth()->toDateString()) }}"
|
||||||
|
style="min-width:160px">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Sampai Tanggal</label>
|
||||||
|
<input type="date" name="end_date" class="form-control"
|
||||||
|
value="{{ request('end_date', now()->toDateString()) }}"
|
||||||
|
style="min-width:160px">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Status Pesanan</label>
|
||||||
|
<select name="status" class="form-select" style="min-width:160px">
|
||||||
|
<option value="">Semua Status</option>
|
||||||
|
<option value="delivered" {{ request('status')=='delivered' ? 'selected':'' }}>Selesai</option>
|
||||||
|
<option value="processing" {{ request('status')=='processing' ? 'selected':'' }}>Diproses</option>
|
||||||
|
<option value="shipped" {{ request('status')=='shipped' ? 'selected':'' }}>Dikirim</option>
|
||||||
|
<option value="cancelled" {{ request('status')=='cancelled' ? 'selected':'' }}>Dibatal</option>
|
||||||
|
<option value="pending" {{ request('status')=='pending' ? 'selected':'' }}>Pending</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Produk</label>
|
||||||
|
<select name="product_id" class="form-select" style="min-width:180px">
|
||||||
|
<option value="">Semua Produk</option>
|
||||||
|
@foreach($products ?? [] as $product)
|
||||||
|
<option value="{{ $product->id }}" {{ request('product_id')==$product->id ? 'selected':'' }}>
|
||||||
|
{{ $product->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-filter">
|
||||||
|
<i class="fas fa-search me-1"></i> Tampilkan
|
||||||
|
</button>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="btn btn-outline-secondary" style="border-radius:8px;padding:10px 18px;font-size:.9rem">
|
||||||
|
<i class="fas fa-redo me-1"></i> Reset
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ===== RINGKASAN ===== --}}
|
||||||
|
<div class="summary-grid">
|
||||||
|
<div class="summary-card pendapatan">
|
||||||
|
<i class="fas fa-wallet summary-icon text-success"></i>
|
||||||
|
<div class="summary-val green">Rp {{ number_format($summary['total_pendapatan'] ?? 0, 0, ',', '.') }}</div>
|
||||||
|
<div class="summary-lbl">Total Pendapatan</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card transaksi">
|
||||||
|
<i class="fas fa-receipt summary-icon text-primary"></i>
|
||||||
|
<div class="summary-val blue">{{ number_format($summary['total_transaksi'] ?? 0) }}</div>
|
||||||
|
<div class="summary-lbl">Total Transaksi</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card produk">
|
||||||
|
<i class="fas fa-box-open summary-icon text-warning"></i>
|
||||||
|
<div class="summary-val orange">{{ number_format($summary['total_produk_keluar'] ?? 0) }}</div>
|
||||||
|
<div class="summary-lbl">Produk Terjual (unit)</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card rata">
|
||||||
|
<i class="fas fa-calculator summary-icon text-purple"></i>
|
||||||
|
<div class="summary-val purple">Rp {{ number_format($summary['rata_per_transaksi'] ?? 0, 0, ',', '.') }}</div>
|
||||||
|
<div class="summary-lbl">Rata-rata per Transaksi</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ===== GRAFIK PENDAPATAN ===== --}}
|
||||||
|
<div class="section-card">
|
||||||
|
<div class="section-header green">
|
||||||
|
<i class="fas fa-chart-area"></i> Grafik Pendapatan Harian
|
||||||
|
</div>
|
||||||
|
<div class="section-body">
|
||||||
|
@if(isset($chartLabels) && count($chartLabels) > 0)
|
||||||
|
<canvas id="revenueChart" height="90"></canvas>
|
||||||
|
@else
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fas fa-chart-area"></i>
|
||||||
|
<p>Belum ada data grafik untuk periode ini.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
{{-- ===== GRAFIK PRODUK TERLARIS ===== --}}
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="section-card mb-0 h-100">
|
||||||
|
<div class="section-header orange">
|
||||||
|
<i class="fas fa-fire"></i> Produk Terlaris
|
||||||
|
</div>
|
||||||
|
<div class="section-body">
|
||||||
|
@if(isset($topProducts) && count($topProducts) > 0)
|
||||||
|
<canvas id="topProductChart" height="220"></canvas>
|
||||||
|
@else
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fas fa-seedling"></i>
|
||||||
|
<p>Belum ada data produk terlaris.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ===== TABEL PRODUK KELUAR ===== --}}
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="section-card mb-0 h-100">
|
||||||
|
<div class="section-header blue">
|
||||||
|
<i class="fas fa-boxes"></i> Detail Produk Keluar
|
||||||
|
</div>
|
||||||
|
<div class="section-body p-0">
|
||||||
|
@if(isset($productSales) && count($productSales) > 0)
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-laporan">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Nama Produk</th>
|
||||||
|
<th class="text-center">Terjual (unit)</th>
|
||||||
|
<th class="text-end">Pendapatan</th>
|
||||||
|
<th>Proporsi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($productSales as $i => $item)
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">{{ $i + 1 }}</td>
|
||||||
|
<td>
|
||||||
|
<strong>{{ $item->product_name }}</strong>
|
||||||
|
<br><small class="text-muted">{{ $item->category ?? '-' }}</small>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span class="badge bg-light text-dark border fw-bold">{{ number_format($item->total_qty) }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end fw-bold text-success">
|
||||||
|
Rp {{ number_format($item->total_revenue, 0, ',', '.') }}
|
||||||
|
</td>
|
||||||
|
<td style="min-width:100px">
|
||||||
|
@php
|
||||||
|
$pct = $summary['total_produk_keluar'] > 0
|
||||||
|
? round($item->total_qty / $summary['total_produk_keluar'] * 100)
|
||||||
|
: 0;
|
||||||
|
@endphp
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<div class="progress progress-thin flex-grow-1">
|
||||||
|
<div class="progress-fill-green" style="width:{{ $pct }}%;height:100%;border-radius:10px"></div>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">{{ $pct }}%</small>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fas fa-box-open"></i>
|
||||||
|
<p>Belum ada data produk untuk periode ini.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ===== TABEL DETAIL TRANSAKSI ===== --}}
|
||||||
|
<div class="section-card">
|
||||||
|
<div class="section-header green">
|
||||||
|
<i class="fas fa-list-alt"></i> Riwayat Transaksi Detail
|
||||||
|
</div>
|
||||||
|
<div class="section-body p-0">
|
||||||
|
@if(isset($orders) && $orders->count() > 0)
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-laporan">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>No. Order</th>
|
||||||
|
<th>Tanggal</th>
|
||||||
|
<th>Pelanggan</th>
|
||||||
|
<th>Produk</th>
|
||||||
|
<th class="text-center">Qty</th>
|
||||||
|
<th class="text-end">Total</th>
|
||||||
|
<th class="text-center">Status</th>
|
||||||
|
<th class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($orders as $order)
|
||||||
|
<tr>
|
||||||
|
<td><code class="text-success">#{{ $order->invoice_number ?? $order->id }}</code></td>
|
||||||
|
<td class="text-muted" style="white-space:nowrap">
|
||||||
|
{{ \Carbon\Carbon::parse($order->created_at)->format('d M Y') }}<br>
|
||||||
|
<small>{{ \Carbon\Carbon::parse($order->created_at)->format('H:i') }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>{{ $order->customer_name ?? $order->user->name ?? 'Guest' }}</strong><br>
|
||||||
|
<small class="text-muted">{{ $order->customer_email ?? $order->user->email ?? '-' }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{-- Relasi di Transaksi adalah 'details', bukan 'items' --}}
|
||||||
|
@forelse($order->details ?? [] as $detail)
|
||||||
|
<div>{{ $detail->product->name ?? '-' }}</div>
|
||||||
|
@empty
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
@endforelse
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
@forelse($order->details ?? [] as $detail)
|
||||||
|
<div>{{ $detail->quantity }}</div>
|
||||||
|
@empty
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
@endforelse
|
||||||
|
</td>
|
||||||
|
<td class="text-end fw-bold text-success" style="white-space:nowrap">
|
||||||
|
Rp {{ number_format($order->total_amount ?? 0, 0, ',', '.') }}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
{{-- Status sesuai nilai enum di DB: pending, processing, shipped, delivered, cancelled --}}
|
||||||
|
@php $orderStatus = $order->order_status ?? 'pending'; @endphp
|
||||||
|
@if($orderStatus == 'delivered')
|
||||||
|
<span class="badge-status badge-selesai">✔ Selesai</span>
|
||||||
|
@elseif($orderStatus == 'shipped')
|
||||||
|
<span class="badge-status badge-kirim">🚚 Dikirim</span>
|
||||||
|
@elseif($orderStatus == 'cancelled')
|
||||||
|
<span class="badge-status badge-batal">✖ Batal</span>
|
||||||
|
@elseif($orderStatus == 'processing')
|
||||||
|
<span class="badge-status badge-proses">⚙ Diproses</span>
|
||||||
|
@else
|
||||||
|
<span class="badge-status" style="background:#e9ecef;color:#6c757d">⏳ Pending</span>
|
||||||
|
@endif
|
||||||
|
{{-- Payment status --}}
|
||||||
|
<br>
|
||||||
|
@php $payStatus = $order->payment_status ?? 'pending'; @endphp
|
||||||
|
@if($payStatus == 'paid')
|
||||||
|
<small class="text-success fw-bold">💳 Paid</small>
|
||||||
|
@elseif($payStatus == 'failed')
|
||||||
|
<small class="text-danger fw-bold">✖ Failed</small>
|
||||||
|
@else
|
||||||
|
<small class="text-warning fw-bold">⏳ Pending</small>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<a href="{{ route('admin.transaksis.show', $order->id) }}"
|
||||||
|
class="btn btn-sm btn-outline-success" title="Lihat Detail">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Pagination --}}
|
||||||
|
@if($orders->hasPages())
|
||||||
|
<div class="d-flex justify-content-between align-items-center px-4 py-3 border-top">
|
||||||
|
<small class="text-muted">
|
||||||
|
Menampilkan {{ $orders->firstItem() }}–{{ $orders->lastItem() }} dari {{ $orders->total() }} transaksi
|
||||||
|
</small>
|
||||||
|
{{ $orders->appends(request()->query())->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fas fa-receipt"></i>
|
||||||
|
<p>Belum ada transaksi untuk periode yang dipilih.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>{{-- end content-area --}}
|
||||||
|
</div>{{-- end main-content --}}
|
||||||
|
</div>{{-- end admin-container --}}
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// ===== GRAFIK PENDAPATAN HARIAN =====
|
||||||
|
@if(isset($chartLabels) && count($chartLabels) > 0)
|
||||||
|
const ctxRevenue = document.getElementById('revenueChart').getContext('2d');
|
||||||
|
new Chart(ctxRevenue, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: {!! json_encode($chartLabels) !!},
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
label: 'Tren',
|
||||||
|
data: {!! json_encode($chartData) !!},
|
||||||
|
borderColor: '#20c997',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 3,
|
||||||
|
tension: 0.4,
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
label: 'Pendapatan (Rp)',
|
||||||
|
data: {!! json_encode($chartData) !!},
|
||||||
|
backgroundColor: 'rgba(40,167,69,0.15)',
|
||||||
|
borderColor: '#28a745',
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderRadius: 6,
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
interaction: { mode: 'index', intersect: false },
|
||||||
|
plugins: {
|
||||||
|
legend: { display: true, position: 'top' },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: ctx => ctx.dataset.label + ': Rp ' + Number(ctx.parsed.y).toLocaleString('id-ID')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: { color: '#f0f0f0' },
|
||||||
|
ticks: { callback: v => 'Rp ' + Number(v).toLocaleString('id-ID') }
|
||||||
|
},
|
||||||
|
x: { grid: { display: false } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
|
||||||
|
// ===== GRAFIK PRODUK TERLARIS (Doughnut) =====
|
||||||
|
@if(isset($topProducts) && count($topProducts) > 0)
|
||||||
|
const ctxProd = document.getElementById('topProductChart').getContext('2d');
|
||||||
|
new Chart(ctxProd, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: {!! json_encode($topProducts->pluck('product_name')) !!},
|
||||||
|
datasets: [{
|
||||||
|
data: {!! json_encode($topProducts->pluck('total_qty')) !!},
|
||||||
|
backgroundColor: ['#28a745','#20c997','#007bff','#fd7e14','#6f42c1','#dc3545','#ffc107'],
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#fff',
|
||||||
|
hoverOffset: 8
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
cutout: '60%',
|
||||||
|
plugins: {
|
||||||
|
legend: { position: 'bottom', labels: { padding: 12, font: { size: 12 } } },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: ctx => ctx.label + ': ' + Number(ctx.parsed).toLocaleString('id-ID') + ' unit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -206,15 +206,18 @@
|
||||||
<a href="{{ route('admin.orders') }}" class="menu-item {{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
<a href="{{ route('admin.orders') }}" class="menu-item {{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
||||||
<i class="fas fa-shopping-cart"></i>Pesanan
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item active">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
|
</a>
|
||||||
<a href="{{ route('admin.users') }}" class="menu-item {{ request()->routeIs('admin.users') ? 'active' : '' }}">
|
<a href="{{ route('admin.users') }}" class="menu-item {{ request()->routeIs('admin.users') ? 'active' : '' }}">
|
||||||
<i class="fas fa-users"></i>Pengguna
|
<i class="fas fa-users"></i>Pengguna
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.reports') }}" class="menu-item {{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
<a href="{{ route('admin.laporan') }}" class="menu-item {{ request()->routeIs('admin.laporan') ? 'active' : '' }}">
|
||||||
<i class="fas fa-chart-line"></i>Laporan
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.settings') }}" class="menu-item {{ request()->routeIs('admin.settings') ? 'active' : '' }}">
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item {{ request()->routeIs('admin.settings') ? 'active' : '' }}">
|
||||||
<i class="fas fa-cog"></i>Pengaturan
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
</a>
|
</a> -->
|
||||||
<div class="mt-4 px-3">
|
<div class="mt-4 px-3">
|
||||||
<form method="POST" action="{{ route('admin.logout') }}">
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,12 @@ class="btn btn-link position-absolute end-0 top-50 translate-middle-y pe-3"
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-end mb-3">
|
||||||
|
<a href="{{ route('admin.password.request') }}"
|
||||||
|
class="text-decoration-none small" style="color: #11998e;">
|
||||||
|
Lupa Password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-login py-3 fw-bold">
|
<button type="submit" class="btn btn-login py-3 fw-bold">
|
||||||
🌿 Masuk ke Dashboard
|
🌿 Masuk ke Dashboard
|
||||||
|
|
@ -133,9 +138,9 @@ class="btn btn-link position-absolute end-0 top-50 translate-middle-y pe-3"
|
||||||
{{-- Debug Info (Hapus di production) --}}
|
{{-- Debug Info (Hapus di production) --}}
|
||||||
@if (config('app.debug'))
|
@if (config('app.debug'))
|
||||||
<div class="mt-4 p-2 bg-light rounded small">
|
<div class="mt-4 p-2 bg-light rounded small">
|
||||||
<strong>Debug Info:</strong><br>
|
<!-- <strong>Debug Info:</strong><br>
|
||||||
Route: {{ Route::currentRouteName() }}<br>
|
Route: {{ Route::currentRouteName() }}<br>
|
||||||
Method: {{ request()->method() }}<br>
|
Method: {{ request()->method() }}<br> -->
|
||||||
@if(request()->method() === 'POST')
|
@if(request()->method() === 'POST')
|
||||||
Data: {{ json_encode(request()->only(['email'])) }}
|
Data: {{ json_encode(request()->only(['email'])) }}
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -1,269 +1,603 @@
|
||||||
@extends('layouts.app')
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Orders Management - Bibit Cabai Admin</title>
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
@section('title', 'Orders Management')
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
@section('content')
|
<!-- Font Awesome -->
|
||||||
<div class="container-fluid">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<!-- Page Header -->
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1 class="h3 mb-0 text-gray-800">Orders Management</h1>
|
|
||||||
<div>
|
|
||||||
<a href="{{ route('admin.transaksis.export') }}" class="btn btn-success">
|
|
||||||
<i class="fas fa-file-excel"></i> Export Excel
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Statistics Cards -->
|
<style>
|
||||||
<div class="row mb-4">
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
<div class="col-xl-3 col-md-6 mb-4">
|
|
||||||
<div class="card border-left-primary shadow h-100 py-2">
|
body {
|
||||||
<div class="card-body">
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
<div class="row no-gutters align-items-center">
|
background: #f8f9fa;
|
||||||
<div class="col mr-2">
|
}
|
||||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
|
|
||||||
Total Orders
|
.admin-container {
|
||||||
</div>
|
display: flex;
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
min-height: 100vh;
|
||||||
{{ $orders->total() }}
|
}
|
||||||
</div>
|
|
||||||
</div>
|
/* Sidebar */
|
||||||
<div class="col-auto">
|
.sidebar {
|
||||||
<i class="fas fa-shopping-cart fa-2x text-gray-300"></i>
|
width: 280px;
|
||||||
</div>
|
background: linear-gradient(180deg, #28a745, #20c997);
|
||||||
|
color: white;
|
||||||
|
min-height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 25px 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h3 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; }
|
||||||
|
|
||||||
|
.sidebar-menu { padding: 20px 0; }
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: block;
|
||||||
|
padding: 15px 25px;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover, .menu-item.active {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-left-color: #fff;
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item i { width: 20px; margin-right: 10px; }
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.main-content {
|
||||||
|
margin-left: 280px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-header {
|
||||||
|
background: white;
|
||||||
|
padding: 20px 30px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-area { padding: 30px; }
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb { background: transparent; padding: 0; margin-bottom: 10px; }
|
||||||
|
.breadcrumb-item a { color: #28a745; text-decoration: none; }
|
||||||
|
|
||||||
|
/* Stats Cards */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-left: 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.primary { border-left-color: #007bff; }
|
||||||
|
.stat-card.warning { border-left-color: #ffc107; }
|
||||||
|
.stat-card.info { border-left-color: #17a2b8; }
|
||||||
|
.stat-card.success { border-left-color: #28a745; }
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.primary .stat-label { color: #007bff; }
|
||||||
|
.stat-card.warning .stat-label { color: #ffc107; }
|
||||||
|
.stat-card.info .stat-label { color: #17a2b8; }
|
||||||
|
.stat-card.success .stat-label { color: #28a745; }
|
||||||
|
|
||||||
|
.stat-value { font-size: 1.5rem; font-weight: 700; color: #333; }
|
||||||
|
.stat-icon { font-size: 2rem; color: #dee2e6; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th, .data-table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table tbody tr:hover { background-color: #f8f9fc; }
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.badge-status {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-pending { background: #fff3cd; color: #856404; }
|
||||||
|
.badge-processing { background: #d1ecf1; color: #0c5460; }
|
||||||
|
.badge-shipped { background: #cce5ff; color: #004085; }
|
||||||
|
.badge-delivered { background: #d4edda; color: #155724; }
|
||||||
|
.badge-cancelled { background: #f8d7da; color: #721c24; }
|
||||||
|
.badge-paid { background: #d4edda; color: #155724; }
|
||||||
|
.badge-failed { background: #f8d7da; color: #721c24; }
|
||||||
|
|
||||||
|
/* Action Buttons */
|
||||||
|
.btn-action {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view { background: #17a2b8; color: white; }
|
||||||
|
.btn-edit { background: #ffc107; color: #212529; }
|
||||||
|
.btn-print { background: #6c757d; color: white; }
|
||||||
|
|
||||||
|
/* Section Header */
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title { font-size: 1.3rem; font-weight: 600; color: #333; }
|
||||||
|
|
||||||
|
/* Back Button */
|
||||||
|
.back-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1100;
|
||||||
|
background: linear-gradient(45deg, #dc3545, #c82333);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button:hover {
|
||||||
|
background: linear-gradient(45deg, #c82333, #a71e2a);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
/* Pagination Fix */
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-item .page-link {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
color: #28a745;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-decoration: none;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-item.active .page-link {
|
||||||
|
background: #28a745;
|
||||||
|
border-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-item.disabled .page-link {
|
||||||
|
color: #adb5bd;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-item .page-link:hover {
|
||||||
|
background: #e9f7ef;
|
||||||
|
border-color: #28a745;
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
/* Cancelled row styling */
|
||||||
|
tr.row-cancelled {
|
||||||
|
background-color: #e9ecef !important;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.row-cancelled td {
|
||||||
|
color: #6c757d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.row-cancelled strong,
|
||||||
|
tr.row-cancelled .text-success {
|
||||||
|
color: #6c757d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.row-cancelled select,
|
||||||
|
tr.row-cancelled .btn-action {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar { width: 100%; position: relative; }
|
||||||
|
.main-content { margin-left: 0; }
|
||||||
|
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Back Button -->
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item">
|
||||||
|
<i class="fas fa-seedling"></i>Kelola Produk
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item active">
|
||||||
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
{{-- ↓ TAMBAHKAN DI SINI ↓ --}}
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item">
|
||||||
|
<i class="fas fa-users"></i>Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="menu-item">
|
||||||
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
|
</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item">
|
||||||
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
|
</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xl-3 col-md-6 mb-4">
|
<!-- Main Content -->
|
||||||
<div class="card border-left-warning shadow h-100 py-2">
|
<div class="main-content">
|
||||||
<div class="card-body">
|
<div class="admin-header">
|
||||||
<div class="row no-gutters align-items-center">
|
<nav aria-label="breadcrumb">
|
||||||
<div class="col mr-2">
|
<ol class="breadcrumb">
|
||||||
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
|
<li class="breadcrumb-item">
|
||||||
Pending Orders
|
<a href="{{ route('admin.dashboard') }}">
|
||||||
</div>
|
<i class="fas fa-home"></i> Dashboard
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
</a>
|
||||||
{{ \App\Models\Transaksi::where('order_status', 'pending')->count() }}
|
</li>
|
||||||
</div>
|
<li class="breadcrumb-item active" aria-current="page">Pesanan</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2>Orders Management</h2>
|
||||||
|
<p class="mb-0">Kelola semua pesanan pelanggan di sini!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area fade-in">
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
@endif
|
||||||
<i class="fas fa-clock fa-2x text-gray-300"></i>
|
|
||||||
|
<!-- Stats Cards -->
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card primary">
|
||||||
|
<div>
|
||||||
|
<div class="stat-label">Total Orders</div>
|
||||||
|
<div class="stat-value">{{ $orders->total() }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon"><i class="fas fa-shopping-cart"></i></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card warning">
|
||||||
|
<div>
|
||||||
|
<div class="stat-label">Pending Orders</div>
|
||||||
|
<div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'pending')->count() }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon"><i class="fas fa-clock"></i></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card info">
|
||||||
|
<div>
|
||||||
|
<div class="stat-label">Processing Orders</div>
|
||||||
|
<div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'processing')->count() }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon"><i class="fas fa-box"></i></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card success">
|
||||||
|
<div>
|
||||||
|
<div class="stat-label">Delivered Orders</div>
|
||||||
|
<div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'delivered')->count() }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon"><i class="fas fa-check-circle"></i></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xl-3 col-md-6 mb-4">
|
<!-- Orders Table -->
|
||||||
<div class="card border-left-info shadow h-100 py-2">
|
<div class="page-header">
|
||||||
<div class="card-body">
|
<div class="section-header">
|
||||||
<div class="row no-gutters align-items-center">
|
<h3 class="section-title">
|
||||||
<div class="col mr-2">
|
<i class="fas fa-shopping-cart text-success me-2"></i>
|
||||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
|
Daftar Pesanan ({{ $orders->total() }})
|
||||||
Processing Orders
|
</h3>
|
||||||
</div>
|
<div class="d-flex gap-2 align-items-center">
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
<select class="form-select form-select-sm" id="filterStatus" onchange="filterOrders()" style="width: 160px;">
|
||||||
{{ \App\Models\Transaksi::where('order_status', 'processing')->count() }}
|
<option value="">Semua Status</option>
|
||||||
|
<option value="pending">Pending</option>
|
||||||
|
<option value="processing">Processing</option>
|
||||||
|
<option value="shipped">Shipped</option>
|
||||||
|
<option value="delivered">Delivered</option>
|
||||||
|
<option value="cancelled">Cancelled</option>
|
||||||
|
</select>
|
||||||
|
<a href="{{ route('admin.transaksis.export') }}" class="btn btn-success btn-sm">
|
||||||
|
<i class="fas fa-file-excel me-1"></i>Export Excel
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
|
||||||
<i class="fas fa-box fa-2x text-gray-300"></i>
|
<div class="table-responsive">
|
||||||
|
<table class="data-table" id="ordersTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Invoice Number</th>
|
||||||
|
<th>Customer</th>
|
||||||
|
<th>Phone</th>
|
||||||
|
<th>Order Status</th>
|
||||||
|
<th>Payment Status</th>
|
||||||
|
<th>Metode Pembayaran</th>
|
||||||
|
<th>Total</th>
|
||||||
|
<th>Order Date</th>
|
||||||
|
<th>Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($orders as $order)
|
||||||
|
<tr data-status="{{ $order->order_status }}"
|
||||||
|
class="{{ $order->order_status === 'cancelled' ? 'row-cancelled' : '' }}">
|
||||||
|
<td><strong>{{ $order->invoice_number }}</strong></td>
|
||||||
|
<td>{{ $order->customer_name }}</td>
|
||||||
|
<td>{{ $order->customer_phone }}</td>
|
||||||
|
<td>
|
||||||
|
<select class="badge-status badge-{{ $order->order_status }} border-0 cursor-pointer"
|
||||||
|
style="cursor:pointer; appearance:auto;"
|
||||||
|
onchange="updateStatus({{ $order->id }}, 'order_status', this.value, this)">
|
||||||
|
<option value="pending" {{ $order->order_status == 'pending' ? 'selected' : '' }}>Pending</option>
|
||||||
|
<option value="processing" {{ $order->order_status == 'processing' ? 'selected' : '' }}>Processing</option>
|
||||||
|
<option value="shipped" {{ $order->order_status == 'shipped' ? 'selected' : '' }}>Shipped</option>
|
||||||
|
<option value="delivered" {{ $order->order_status == 'delivered' ? 'selected' : '' }}>Delivered</option>
|
||||||
|
<option value="cancelled" {{ $order->order_status == 'cancelled' ? 'selected' : '' }}>Cancelled</option>
|
||||||
|
</select>
|
||||||
|
@if($order->cancellation && $order->cancellation->status === 'pending')
|
||||||
|
<br><span class="badge-status badge-warning mt-1" style="font-size:0.7rem;">
|
||||||
|
⚠ Minta Batal
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select class="badge-status badge-{{ $order->payment_status }} border-0"
|
||||||
|
style="cursor:pointer; appearance:auto;"
|
||||||
|
onchange="updateStatus({{ $order->id }}, 'payment_status', this.value, this)">
|
||||||
|
<option value="pending" {{ $order->payment_status == 'pending' ? 'selected' : '' }}>Pending</option>
|
||||||
|
<option value="paid" {{ $order->payment_status == 'paid' ? 'selected' : '' }}>Paid</option>
|
||||||
|
<option value="failed" {{ $order->payment_status == 'failed' ? 'selected' : '' }}>Failed</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge-status" style="background:#ced4da;color:#212529;">
|
||||||
|
{{ ucwords(str_replace('_', ' ', $order->payment_method ?? '-')) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td><strong class="text-success">Rp {{ number_format($order->total_amount, 0, ',', '.') }}</strong></td>
|
||||||
|
<td><small>{{ $order->created_at->format('d M Y H:i') }}</small></td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-1">
|
||||||
|
<a href="{{ route('admin.transaksis.show', $order->id) }}"
|
||||||
|
class="btn-action btn-view" title="View Details">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.transaksis.edit', $order->id) }}"
|
||||||
|
class="btn-action btn-edit" title="Edit">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.transaksis.print', $order->id) }}"
|
||||||
|
class="btn-action btn-print" title="Print Invoice" target="_blank">
|
||||||
|
<i class="fas fa-print"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||||
|
<small class="text-muted">
|
||||||
|
Menampilkan {{ $orders->firstItem() ?? 0 }} sampai {{ $orders->lastItem() ?? 0 }}
|
||||||
|
dari {{ $orders->total() }} pesanan
|
||||||
|
</small>
|
||||||
|
<div>{{ $orders->links() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xl-3 col-md-6 mb-4">
|
<!-- Bootstrap JS -->
|
||||||
<div class="card border-left-success shadow h-100 py-2">
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<div class="card-body">
|
|
||||||
<div class="row no-gutters align-items-center">
|
|
||||||
<div class="col mr-2">
|
|
||||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
|
|
||||||
Delivered Orders
|
|
||||||
</div>
|
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
|
||||||
{{ \App\Models\Transaksi::where('order_status', 'delivered')->count() }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<i class="fas fa-check-circle fa-2x text-gray-300"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Orders Table -->
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3 d-flex justify-content-between align-items-center">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Orders List</h6>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<!-- Filter by Status -->
|
|
||||||
<select class="form-control form-control-sm" id="filterStatus" onchange="filterOrders()">
|
|
||||||
<option value="">All Status</option>
|
|
||||||
<option value="pending">Pending</option>
|
|
||||||
<option value="processing">Processing</option>
|
|
||||||
<option value="shipped">Shipped</option>
|
|
||||||
<option value="delivered">Delivered</option>
|
|
||||||
<option value="cancelled">Cancelled</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-bordered table-hover" id="ordersTable" width="100%" cellspacing="0">
|
|
||||||
<thead class="thead-light">
|
|
||||||
<tr>
|
|
||||||
<th>Invoice Number</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Phone</th>
|
|
||||||
<th>Shipping Address</th>
|
|
||||||
<th>City</th>
|
|
||||||
<th>Province</th>
|
|
||||||
<th>Postal Code</th>
|
|
||||||
<th>Order Status</th>
|
|
||||||
<th>Payment Status</th>
|
|
||||||
<th>Total</th>
|
|
||||||
<th>Order Date</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@forelse($orders as $order)
|
|
||||||
<tr data-status="{{ $order->order_status }}">
|
|
||||||
<td>
|
|
||||||
<strong>{{ $order->invoice_number }}</strong>
|
|
||||||
</td>
|
|
||||||
<td>{{ $order->customer_name }}</td>
|
|
||||||
<td>{{ $order->customer_phone }}</td>
|
|
||||||
<td>
|
|
||||||
<small>{{ \Illuminate\Support\Str::limit($order->shipping_address, 30) }}</small>
|
|
||||||
</td>
|
|
||||||
<td>{{ $order->city }}</td>
|
|
||||||
<td>{{ $order->province }}</td>
|
|
||||||
<td>{{ $order->postal_code }}</td>
|
|
||||||
<td>
|
|
||||||
@php
|
|
||||||
$statusClass = [
|
|
||||||
'pending' => 'warning',
|
|
||||||
'processing' => 'info',
|
|
||||||
'shipped' => 'primary',
|
|
||||||
'delivered' => 'success',
|
|
||||||
'cancelled' => 'danger'
|
|
||||||
][$order->order_status] ?? 'secondary';
|
|
||||||
@endphp
|
|
||||||
<span class="badge badge-{{ $statusClass }}">
|
|
||||||
{{ ucfirst($order->order_status) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@php
|
|
||||||
$paymentClass = [
|
|
||||||
'pending' => 'warning',
|
|
||||||
'paid' => 'success',
|
|
||||||
'failed' => 'danger'
|
|
||||||
][$order->payment_status] ?? 'secondary';
|
|
||||||
@endphp
|
|
||||||
<span class="badge badge-{{ $paymentClass }}">
|
|
||||||
{{ ucfirst($order->payment_status) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>Rp {{ number_format($order->total_amount, 0, ',', '.') }}</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<small>{{ $order->created_at->format('d M Y H:i') }}</small>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
|
||||||
<a href="{{ route('admin.transaksis.show', $order->id) }}"
|
|
||||||
class="btn btn-info btn-sm"
|
|
||||||
title="View Details">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{{ route('admin.transaksis.edit', $order->id) }}"
|
|
||||||
class="btn btn-warning btn-sm"
|
|
||||||
title="Edit">
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{{ route('admin.transaksis.print', $order->id) }}"
|
|
||||||
class="btn btn-secondary btn-sm"
|
|
||||||
title="Print Invoice"
|
|
||||||
target="_blank">
|
|
||||||
<i class="fas fa-print"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@empty
|
|
||||||
<tr>
|
|
||||||
<td colspan="12" class="text-center py-4">
|
|
||||||
<i class="fas fa-inbox fa-3x text-gray-300 mb-3"></i>
|
|
||||||
<p class="text-gray-500">No orders found</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Pagination -->
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
||||||
<div>
|
|
||||||
Showing {{ $orders->firstItem() ?? 0 }} to {{ $orders->lastItem() ?? 0 }}
|
|
||||||
of {{ $orders->total() }} orders
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ $orders->links() }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@push('styles')
|
|
||||||
<style>
|
|
||||||
.gap-2 {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-hover tbody tr:hover {
|
|
||||||
background-color: #f8f9fc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-group-sm .btn {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.35em 0.65em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script>
|
<script>
|
||||||
function filterOrders() {
|
function filterOrders() {
|
||||||
const filterValue = document.getElementById('filterStatus').value.toLowerCase();
|
const filterValue = document.getElementById('filterStatus').value.toLowerCase();
|
||||||
const table = document.getElementById('ordersTable');
|
const rows = document.querySelectorAll('#ordersTable tbody tr[data-status]');
|
||||||
const rows = table.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
rows.forEach(row => {
|
||||||
|
const status = row.getAttribute('data-status');
|
||||||
|
row.style.display = (filterValue === '' || status === filterValue) ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
function updateStatus(orderId, field, value, selectEl) {
|
||||||
const status = rows[i].getAttribute('data-status');
|
// Jika status sudah cancelled, tidak bisa diubah
|
||||||
|
if (field === 'order_status') {
|
||||||
|
const row = selectEl.closest('tr');
|
||||||
|
const currentStatus = row.getAttribute('data-status');
|
||||||
|
if (currentStatus === 'cancelled' || currentStatus === 'delivered') {
|
||||||
|
alert('Pesanan yang sudah selesai atau dibatalkan tidak dapat diubah kembali.');
|
||||||
|
selectEl.value = currentStatus;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (filterValue === '' || status === filterValue) {
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
rows[i].style.display = '';
|
|
||||||
} else {
|
fetch(`/admin/orders/${orderId}/update-status`, {
|
||||||
rows[i].style.display = 'none';
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ [field]: value })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Update warna badge sesuai nilai baru
|
||||||
|
const badgeClasses = ['badge-pending','badge-processing','badge-shipped','badge-delivered','badge-cancelled','badge-paid','badge-failed'];
|
||||||
|
selectEl.classList.remove(...badgeClasses);
|
||||||
|
selectEl.classList.add('badge-' + value);
|
||||||
|
|
||||||
|
// Update data-status di row jika yang diubah adalah order_status
|
||||||
|
if (field === 'order_status') {
|
||||||
|
const row = selectEl.closest('tr');
|
||||||
|
row.setAttribute('data-status', value);
|
||||||
|
|
||||||
|
// Tambahkan/hapus class abu-abu jika status cancelled
|
||||||
|
if (value === 'cancelled') {
|
||||||
|
row.classList.add('row-cancelled');
|
||||||
|
} else {
|
||||||
|
row.classList.remove('row-cancelled');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Tampilkan notifikasi kecil
|
||||||
|
showToast('Status berhasil diupdate!');
|
||||||
|
} else {
|
||||||
|
alert('Gagal update: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => alert('Terjadi kesalahan koneksi.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(msg) {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.innerText = msg;
|
||||||
|
toast.style.cssText = 'position:fixed;bottom:20px;right:20px;background:#28a745;color:white;padding:10px 20px;border-radius:8px;z-index:9999;font-size:14px;';
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
setTimeout(() => toast.remove(), 2500);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
</body>
|
||||||
@endsection
|
</html>
|
||||||
|
|
@ -237,12 +237,12 @@
|
||||||
<a class="menu-item" href="{{ route('admin.users') }}">
|
<a class="menu-item" href="{{ route('admin.users') }}">
|
||||||
<i class="fas fa-users"></i>Pengguna
|
<i class="fas fa-users"></i>Pengguna
|
||||||
</a>
|
</a>
|
||||||
<a class="menu-item" href="{{ route('admin.reports') }}">
|
<a class="menu-item" href="{{ route('admin.laporan') }}">
|
||||||
<i class="fas fa-chart-line"></i>Laporan
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
</a>
|
</a>
|
||||||
<a class="menu-item" href="{{ route('admin.settings') }}">
|
<!-- <a class="menu-item" href="{{ route('admin.settings') }}">
|
||||||
<i class="fas fa-cog"></i>Pengaturan
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
</a>
|
</a> -->
|
||||||
<div class="mt-4 px-3">
|
<div class="mt-4 px-3">
|
||||||
<form method="POST" action="{{ route('admin.logout') }}">
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
|
||||||
|
|
@ -411,16 +411,20 @@
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.orders') }}" class="menu-item">
|
<a href="{{ route('admin.orders') }}" class="menu-item">
|
||||||
<i class="fas fa-shopping-cart"></i>Pesanan
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
{{-- ↓ TAMBAHKAN DI SINI ↓ --}}
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.users') }}" class="menu-item">
|
<a href="{{ route('admin.users') }}" class="menu-item">
|
||||||
<i class="fas fa-users"></i>Pengguna
|
<i class="fas fa-users"></i>Pengguna
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.reports') }}" class="menu-item">
|
<a href="{{ route('admin.laporan') }}" class="menu-item">
|
||||||
<i class="fas fa-chart-line"></i>Laporan
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.settings') }}" class="menu-item">
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item">
|
||||||
<i class="fas fa-cog"></i>Pengaturan
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
</a>
|
</a> -->
|
||||||
<div class="mt-4 px-3">
|
<div class="mt-4 px-3">
|
||||||
<form method="POST" action="{{ route('admin.logout') }}">
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Reset Password Admin - TA Bibit Cabai</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.btn-primary-custom {
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.btn-primary-custom:hover {
|
||||||
|
background: linear-gradient(135deg, #0d7377 0%, #2dd654 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(17, 153, 142, 0.3);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.btn-primary-custom:disabled {
|
||||||
|
background: #adb5bd;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #11998e;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(17, 153, 142, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-wrapper {
|
||||||
|
display: flex; align-items: center; justify-content: center; margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
.step-circle {
|
||||||
|
width: 36px; height: 36px; border-radius: 50%;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-weight: 700; font-size: 13px;
|
||||||
|
}
|
||||||
|
.step-circle.active { background: #11998e; color: #fff; }
|
||||||
|
.step-circle.done { background: #11998e; color: #fff; }
|
||||||
|
.step-circle.idle { background: #dee2e6; color: #888; }
|
||||||
|
.step-label { font-size: 11px; white-space: nowrap; margin-top: 4px; }
|
||||||
|
.step-label.active { color: #11998e; font-weight: 700; }
|
||||||
|
.step-label.done { color: #11998e; }
|
||||||
|
.step-label.idle { color: #aaa; }
|
||||||
|
.step-item { display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.step-line { height: 2px; width: 36px; margin: 0 6px 18px; }
|
||||||
|
.step-line.done { background: #11998e; }
|
||||||
|
.step-line.idle { background: #dee2e6; }
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
width: 64px; height: 64px;
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-4 col-lg-4">
|
||||||
|
<div class="card login-card">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
|
||||||
|
<div class="icon-box">🔓</div>
|
||||||
|
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<h4 class="fw-bold text-dark mb-1">Password Baru Admin</h4>
|
||||||
|
<p class="text-muted small">Buat password baru yang kuat untuk akun admin</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Step Indicator --}}
|
||||||
|
<div class="step-wrapper">
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle done">✓</div>
|
||||||
|
<div class="step-label done">Email</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line done"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle done">✓</div>
|
||||||
|
<div class="step-label done">Kode OTP</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line done"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle active">3</div>
|
||||||
|
<div class="step-label active">Password Baru</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show py-2" role="alert">
|
||||||
|
<small>
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<div>⚠️ {{ $error }}</div>
|
||||||
|
@endforeach
|
||||||
|
</small>
|
||||||
|
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show py-2" role="alert">
|
||||||
|
<small>⚠️ {{ session('error') }}</small>
|
||||||
|
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.password.update') }}" id="resetForm" novalidate>
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="token" value="{{ session('admin_reset_token') }}">
|
||||||
|
<input type="hidden" name="email" value="{{ session('admin_reset_email') }}">
|
||||||
|
|
||||||
|
{{-- New Password --}}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label fw-semibold">Password Baru</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password"
|
||||||
|
class="form-control @error('password') is-invalid @enderror"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Minimal 8 karakter"
|
||||||
|
required>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="togglePw1">👁️</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text text-muted small">ℹ️ Minimal 8 karakter, tanpa spasi</div>
|
||||||
|
@error('password')
|
||||||
|
<div class="text-danger small mt-1">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
<div id="pwError" class="text-danger small mt-1" style="display:none;">
|
||||||
|
⚠️ Password minimal 8 karakter dan tidak boleh mengandung spasi
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Strength Bar --}}
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<small class="text-muted">Kekuatan password:</small>
|
||||||
|
<small id="strengthLabel" class="fw-semibold text-secondary">—</small>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height:6px;border-radius:3px;">
|
||||||
|
<div id="strengthBar" class="progress-bar bg-secondary" style="width:0%;transition:width .3s;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Confirm Password --}}
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="password_confirmation" class="form-label fw-semibold">Konfirmasi Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password"
|
||||||
|
class="form-control @error('password_confirmation') is-invalid @enderror"
|
||||||
|
id="password_confirmation"
|
||||||
|
name="password_confirmation"
|
||||||
|
placeholder="Ulangi password baru"
|
||||||
|
required>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="togglePw2">👁️</button>
|
||||||
|
</div>
|
||||||
|
@error('password_confirmation')
|
||||||
|
<div class="text-danger small mt-1">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
<div id="confirmError" class="text-danger small mt-1" style="display:none;">
|
||||||
|
⚠️ Password tidak cocok
|
||||||
|
</div>
|
||||||
|
<div id="confirmOk" class="text-success small mt-1" style="display:none;">
|
||||||
|
✅ Password cocok
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary-custom py-2 fw-bold" id="submitBtn" disabled>
|
||||||
|
💾 Simpan Password Baru
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const pw = document.getElementById('password');
|
||||||
|
const conf = document.getElementById('password_confirmation');
|
||||||
|
const btn = document.getElementById('submitBtn');
|
||||||
|
const bar = document.getElementById('strengthBar');
|
||||||
|
const label = document.getElementById('strengthLabel');
|
||||||
|
const pwErr = document.getElementById('pwError');
|
||||||
|
const cErr = document.getElementById('confirmError');
|
||||||
|
const cOk = document.getElementById('confirmOk');
|
||||||
|
|
||||||
|
// Toggle visibility
|
||||||
|
document.getElementById('togglePw1').addEventListener('click', () => {
|
||||||
|
pw.type = pw.type === 'password' ? 'text' : 'password';
|
||||||
|
document.getElementById('togglePw1').textContent = pw.type === 'password' ? '👁️' : '🙈';
|
||||||
|
});
|
||||||
|
document.getElementById('togglePw2').addEventListener('click', () => {
|
||||||
|
conf.type = conf.type === 'password' ? 'text' : 'password';
|
||||||
|
document.getElementById('togglePw2').textContent = conf.type === 'password' ? '👁️' : '🙈';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Strength checker
|
||||||
|
function strength(v) {
|
||||||
|
let s = 0;
|
||||||
|
if (v.length >= 8) s++;
|
||||||
|
if (v.length >= 12) s++;
|
||||||
|
if (/[A-Z]/.test(v)) s++;
|
||||||
|
if (/[0-9]/.test(v)) s++;
|
||||||
|
if (/[^A-Za-z0-9]/.test(v)) s++;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.addEventListener('input', function () {
|
||||||
|
const v = this.value;
|
||||||
|
const invalid = v.length > 0 && (v.length < 8 || /\s/.test(v));
|
||||||
|
pwErr.style.display = invalid ? 'block' : 'none';
|
||||||
|
this.classList.toggle('is-invalid', invalid);
|
||||||
|
|
||||||
|
// Strength bar
|
||||||
|
if (v.length === 0) {
|
||||||
|
bar.style.width = '0%'; bar.className = 'progress-bar bg-secondary';
|
||||||
|
label.textContent = '—'; label.className = 'fw-semibold text-secondary';
|
||||||
|
} else {
|
||||||
|
const lvls = [
|
||||||
|
{ w:'20%', c:'bg-danger', l:'Sangat Lemah', lc:'text-danger' },
|
||||||
|
{ w:'40%', c:'bg-warning', l:'Lemah', lc:'text-warning' },
|
||||||
|
{ w:'60%', c:'bg-info', l:'Sedang', lc:'text-info' },
|
||||||
|
{ w:'80%', c:'bg-primary', l:'Kuat', lc:'text-primary' },
|
||||||
|
{ w:'100%',c:'bg-success', l:'Sangat Kuat', lc:'text-success' },
|
||||||
|
];
|
||||||
|
const lvl = lvls[Math.min(strength(v) - 1, 4)] || lvls[0];
|
||||||
|
bar.style.width = lvl.w; bar.className = `progress-bar ${lvl.c}`;
|
||||||
|
label.textContent = lvl.l; label.className = `fw-semibold ${lvl.lc}`;
|
||||||
|
}
|
||||||
|
checkConf(); checkBtn();
|
||||||
|
});
|
||||||
|
|
||||||
|
conf.addEventListener('input', function () { checkConf(); checkBtn(); });
|
||||||
|
|
||||||
|
function checkConf() {
|
||||||
|
if (conf.value.length === 0) {
|
||||||
|
cErr.style.display = 'none'; cOk.style.display = 'none';
|
||||||
|
conf.classList.remove('is-valid', 'is-invalid');
|
||||||
|
} else if (conf.value !== pw.value) {
|
||||||
|
cErr.style.display = 'block'; cOk.style.display = 'none';
|
||||||
|
conf.classList.add('is-invalid'); conf.classList.remove('is-valid');
|
||||||
|
} else {
|
||||||
|
cErr.style.display = 'none'; cOk.style.display = 'block';
|
||||||
|
conf.classList.remove('is-invalid'); conf.classList.add('is-valid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBtn() {
|
||||||
|
const pwOk = pw.value.length >= 8 && !/\s/.test(pw.value);
|
||||||
|
const confOk = conf.value === pw.value && conf.value.length > 0;
|
||||||
|
btn.disabled = !(pwOk && confOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
[pw, conf].forEach(i => i.addEventListener('keydown', e => { if (e.key === ' ') e.preventDefault(); }));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,258 +1,371 @@
|
||||||
@extends('layouts.app')
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Edit Order - Bibit Cabai Admin</title>
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f8f9fa; }
|
||||||
|
.admin-container { display: flex; min-height: 100vh; }
|
||||||
|
|
||||||
@section('title', 'Edit Order')
|
.sidebar {
|
||||||
|
width: 280px; background: linear-gradient(180deg, #28a745, #20c997);
|
||||||
|
color: white; min-height: 100vh; position: fixed; top: 0; left: 0; z-index: 1000;
|
||||||
|
}
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 25px 20px; text-align: center;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.sidebar-header h3 { font-size: 1.8rem; font-weight: bold; margin-bottom: 5px; }
|
||||||
|
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; }
|
||||||
|
.sidebar-menu { padding: 20px 0; }
|
||||||
|
.menu-item {
|
||||||
|
display: block; padding: 15px 25px; color: white;
|
||||||
|
text-decoration: none; transition: all 0.3s; border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
.menu-item:hover, .menu-item.active {
|
||||||
|
background: rgba(255,255,255,0.2); color: white;
|
||||||
|
border-left-color: #fff; transform: translateX(5px); text-decoration: none;
|
||||||
|
}
|
||||||
|
.menu-item i { width: 20px; margin-right: 10px; }
|
||||||
|
|
||||||
@section('content')
|
.main-content { margin-left: 280px; flex: 1; min-height: 100vh; }
|
||||||
<div class="container-fluid">
|
.admin-header {
|
||||||
<!-- Page Header -->
|
background: white; padding: 20px 30px;
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-bottom: 1px solid #dee2e6;
|
||||||
<div>
|
}
|
||||||
<h1 class="h3 mb-0 text-gray-800">Edit Order</h1>
|
.content-area { padding: 30px; }
|
||||||
|
.breadcrumb { background: transparent; padding: 0; margin-bottom: 10px; }
|
||||||
|
.breadcrumb-item a { color: #28a745; text-decoration: none; }
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
position: fixed; top: 20px; right: 20px; z-index: 1100;
|
||||||
|
background: linear-gradient(45deg, #dc3545, #c82333);
|
||||||
|
color: white; border: none; padding: 12px 20px;
|
||||||
|
border-radius: 25px; font-weight: 600; text-decoration: none; transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.back-button:hover { color: white; transform: translateY(-2px); }
|
||||||
|
|
||||||
|
.fade-in { animation: fadeIn 0.3s ease-in-out; }
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar { width: 100%; position: relative; }
|
||||||
|
.main-content { margin-left: 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
|
||||||
|
{{-- Sidebar --}}
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item">
|
||||||
|
<i class="fas fa-seedling"></i>Kelola Produk
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item active">
|
||||||
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item">
|
||||||
|
<i class="fas fa-users"></i>Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="menu-item">
|
||||||
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
|
</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item">
|
||||||
|
<i class="fas fa-cog"></i>Pengaturan -->
|
||||||
|
</a>
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{-- Main Content --}}
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="admin-header">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb bg-transparent p-0 mb-0">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
|
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{{ route('admin.orders') }}">Orders</a></li>
|
<li class="breadcrumb-item"><a href="{{ route('admin.orders') }}">Pesanan</a></li>
|
||||||
<li class="breadcrumb-item active">Edit {{ $transaksi->invoice_number }}</li>
|
<li class="breadcrumb-item active">Edit {{ $transaksi->invoice_number }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h2>Edit Order</h2>
|
||||||
|
<p class="mb-0">{{ $transaksi->invoice_number }}</p>
|
||||||
|
</div>
|
||||||
|
<a href="{{ route('admin.transaksis.show', $transaksi->id) }}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> Back
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area fade-in">
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
<strong>Error!</strong>
|
||||||
|
<ul class="mb-0 mt-2">
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<li>{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form action="{{ route('admin.transaksis.update', $transaksi->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
{{-- Kolom Kiri --}}
|
||||||
|
<div class="col-lg-8">
|
||||||
|
|
||||||
|
{{-- Order Details --}}
|
||||||
|
<div class="card shadow mb-4" style="border-radius:12px;">
|
||||||
|
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">Order Details</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Invoice Number</label>
|
||||||
|
<input type="text" class="form-control bg-light"
|
||||||
|
value="{{ $transaksi->invoice_number }}" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Order Status <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-select @error('order_status') is-invalid @enderror"
|
||||||
|
name="order_status" required>
|
||||||
|
@foreach(['pending','processing','shipped','delivered','cancelled'] as $s)
|
||||||
|
<option value="{{ $s }}"
|
||||||
|
{{ old('order_status', $transaksi->order_status) == $s ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst($s) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('order_status')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Payment Status <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-select @error('payment_status') is-invalid @enderror"
|
||||||
|
name="payment_status" required>
|
||||||
|
@foreach(['pending','paid','failed'] as $p)
|
||||||
|
<option value="{{ $p }}"
|
||||||
|
{{ old('payment_status', $transaksi->payment_status) == $p ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst($p) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('payment_status')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Tracking Number</label>
|
||||||
|
<input type="text" class="form-control @error('tracking_number') is-invalid @enderror"
|
||||||
|
name="tracking_number"
|
||||||
|
value="{{ old('tracking_number', $transaksi->tracking_number) }}"
|
||||||
|
placeholder="Enter tracking number">
|
||||||
|
@error('tracking_number')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Notes</label>
|
||||||
|
<textarea class="form-control @error('notes') is-invalid @enderror"
|
||||||
|
name="notes" rows="3"
|
||||||
|
placeholder="Enter any notes...">{{ old('notes', $transaksi->notes) }}</textarea>
|
||||||
|
@error('notes')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Customer Information (readonly) --}}
|
||||||
|
<div class="card shadow mb-4" style="border-radius:12px;">
|
||||||
|
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">
|
||||||
|
Customer Information
|
||||||
|
<small class="text-muted fw-normal ms-2" style="font-size:0.75rem;">
|
||||||
|
<i class="fas fa-lock"></i> Tidak dapat diubah
|
||||||
|
</small>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Customer Name</label>
|
||||||
|
<input type="text" class="form-control bg-light"
|
||||||
|
value="{{ $transaksi->customer_name }}" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Phone</label>
|
||||||
|
<input type="text" class="form-control bg-light"
|
||||||
|
value="{{ $transaksi->customer_phone }}" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Email</label>
|
||||||
|
<input type="text" class="form-control bg-light"
|
||||||
|
value="{{ $transaksi->customer_email ?? '-' }}" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Shipping Information (readonly) --}}
|
||||||
|
<div class="card shadow mb-4" style="border-radius:12px;">
|
||||||
|
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">
|
||||||
|
Shipping Information
|
||||||
|
<small class="text-muted fw-normal ms-2" style="font-size:0.75rem;">
|
||||||
|
<i class="fas fa-lock"></i> Tidak dapat diubah
|
||||||
|
</small>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Shipping Address</label>
|
||||||
|
<textarea class="form-control bg-light" rows="3"
|
||||||
|
readonly>{{ $transaksi->shipping_address }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">City</label>
|
||||||
|
<input type="text" class="form-control bg-light"
|
||||||
|
value="{{ $transaksi->city }}" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Province</label>
|
||||||
|
<input type="text" class="form-control bg-light"
|
||||||
|
value="{{ $transaksi->province }}" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Postal Code</label>
|
||||||
|
<input type="text" class="form-control bg-light"
|
||||||
|
value="{{ $transaksi->postal_code }}" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{-- Akhir col-lg-8 --}}
|
||||||
|
|
||||||
|
{{-- Kolom Kanan --}}
|
||||||
|
<div class="col-lg-4">
|
||||||
|
|
||||||
|
{{-- Order Summary --}}
|
||||||
|
<div class="card shadow mb-4" style="border-radius:12px;">
|
||||||
|
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">Order Summary</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="text-primary">{{ $transaksi->invoice_number }}</h5>
|
||||||
|
<p class="text-muted mb-3">{{ $transaksi->created_at->format('d M Y, H:i') }}</p>
|
||||||
|
<table class="table table-sm table-borderless">
|
||||||
|
<tbody>
|
||||||
|
@foreach($transaksi->details as $detail)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $detail->product->name ?? 'N/A' }}</td>
|
||||||
|
<td class="text-end">x{{ $detail->quantity }}</td>
|
||||||
|
<td class="text-end">Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="border-top">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Total:</th>
|
||||||
|
<th class="text-end">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Action Buttons --}}
|
||||||
|
<div class="card shadow" style="border-radius:12px;">
|
||||||
|
<div class="card-body">
|
||||||
|
<button type="submit" class="btn btn-primary w-100 mb-2">
|
||||||
|
<i class="fas fa-save me-1"></i> Update Order
|
||||||
|
</button>
|
||||||
|
<a href="{{ route('admin.transaksis.show', $transaksi->id) }}"
|
||||||
|
class="btn btn-secondary w-100">
|
||||||
|
<i class="fas fa-times me-1"></i> Cancel
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{-- Akhir col-lg-4 --}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('admin.transaksis.show', $transaksi->id) }}" class="btn btn-secondary">
|
|
||||||
<i class="fas fa-arrow-left"></i> Back
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($errors->any())
|
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
||||||
<strong>Error!</strong> Please fix the following issues:
|
|
||||||
<ul class="mb-0 mt-2">
|
|
||||||
@foreach ($errors->all() as $error)
|
|
||||||
<li>{{ $error }}</li>
|
|
||||||
@endforeach
|
|
||||||
</ul>
|
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<form action="{{ route('admin.transaksis.update', $transaksi->id) }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
@method('PUT')
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<!-- Order Details -->
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Order Details</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="invoice_number">Invoice Number</label>
|
|
||||||
<input type="text" class="form-control" id="invoice_number" name="invoice_number"
|
|
||||||
value="{{ old('invoice_number', $transaksi->invoice_number) }}" readonly>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="order_status">Order Status <span class="text-danger">*</span></label>
|
|
||||||
<select class="form-control @error('order_status') is-invalid @enderror"
|
|
||||||
id="order_status" name="order_status" required>
|
|
||||||
<option value="pending" {{ old('order_status', $transaksi->order_status) == 'pending' ? 'selected' : '' }}>Pending</option>
|
|
||||||
<option value="processing" {{ old('order_status', $transaksi->order_status) == 'processing' ? 'selected' : '' }}>Processing</option>
|
|
||||||
<option value="shipped" {{ old('order_status', $transaksi->order_status) == 'shipped' ? 'selected' : '' }}>Shipped</option>
|
|
||||||
<option value="delivered" {{ old('order_status', $transaksi->order_status) == 'delivered' ? 'selected' : '' }}>Delivered</option>
|
|
||||||
<option value="cancelled" {{ old('order_status', $transaksi->order_status) == 'cancelled' ? 'selected' : '' }}>Cancelled</option>
|
|
||||||
</select>
|
|
||||||
@error('order_status')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="payment_status">Payment Status <span class="text-danger">*</span></label>
|
|
||||||
<select class="form-control @error('payment_status') is-invalid @enderror"
|
|
||||||
id="payment_status" name="payment_status" required>
|
|
||||||
<option value="pending" {{ old('payment_status', $transaksi->payment_status) == 'pending' ? 'selected' : '' }}>Pending</option>
|
|
||||||
<option value="paid" {{ old('payment_status', $transaksi->payment_status) == 'paid' ? 'selected' : '' }}>Paid</option>
|
|
||||||
<option value="failed" {{ old('payment_status', $transaksi->payment_status) == 'failed' ? 'selected' : '' }}>Failed</option>
|
|
||||||
</select>
|
|
||||||
@error('payment_status')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tracking_number">Tracking Number</label>
|
|
||||||
<input type="text" class="form-control @error('tracking_number') is-invalid @enderror"
|
|
||||||
id="tracking_number" name="tracking_number"
|
|
||||||
value="{{ old('tracking_number', $transaksi->tracking_number) }}"
|
|
||||||
placeholder="Enter tracking number">
|
|
||||||
@error('tracking_number')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="notes">Notes</label>
|
|
||||||
<textarea class="form-control @error('notes') is-invalid @enderror"
|
|
||||||
id="notes" name="notes" rows="3"
|
|
||||||
placeholder="Enter any notes about this order">{{ old('notes', $transaksi->notes) }}</textarea>
|
|
||||||
@error('notes')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Customer Information -->
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Customer Information</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="customer_name">Customer Name <span class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control @error('customer_name') is-invalid @enderror"
|
|
||||||
id="customer_name" name="customer_name"
|
|
||||||
value="{{ old('customer_name', $transaksi->customer_name) }}" required>
|
|
||||||
@error('customer_name')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="customer_phone">Phone <span class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control @error('customer_phone') is-invalid @enderror"
|
|
||||||
id="customer_phone" name="customer_phone"
|
|
||||||
value="{{ old('customer_phone', $transaksi->customer_phone) }}" required>
|
|
||||||
@error('customer_phone')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="customer_email">Email</label>
|
|
||||||
<input type="email" class="form-control @error('customer_email') is-invalid @enderror"
|
|
||||||
id="customer_email" name="customer_email"
|
|
||||||
value="{{ old('customer_email', $transaksi->customer_email) }}">
|
|
||||||
@error('customer_email')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Shipping Information -->
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Shipping Information</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="shipping_address">Shipping Address <span class="text-danger">*</span></label>
|
|
||||||
<textarea class="form-control @error('shipping_address') is-invalid @enderror"
|
|
||||||
id="shipping_address" name="shipping_address" rows="3" required>{{ old('shipping_address', $transaksi->shipping_address) }}</textarea>
|
|
||||||
@error('shipping_address')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="city">City <span class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control @error('city') is-invalid @enderror"
|
|
||||||
id="city" name="city"
|
|
||||||
value="{{ old('city', $transaksi->city) }}" required>
|
|
||||||
@error('city')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="province">Province <span class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control @error('province') is-invalid @enderror"
|
|
||||||
id="province" name="province"
|
|
||||||
value="{{ old('province', $transaksi->province) }}" required>
|
|
||||||
@error('province')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="postal_code">Postal Code <span class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control @error('postal_code') is-invalid @enderror"
|
|
||||||
id="postal_code" name="postal_code"
|
|
||||||
value="{{ old('postal_code', $transaksi->postal_code) }}" required>
|
|
||||||
@error('postal_code')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Order Summary -->
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Order Summary</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="text-primary">{{ $transaksi->invoice_number }}</h5>
|
|
||||||
<p class="text-muted mb-3">{{ $transaksi->created_at->format('d M Y, H:i') }}</p>
|
|
||||||
|
|
||||||
<table class="table table-sm table-borderless">
|
|
||||||
<tbody>
|
|
||||||
@foreach($transaksi->details as $detail)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $detail->product->name ?? 'N/A' }}</td>
|
|
||||||
<td class="text-right">x{{ $detail->quantity }}</td>
|
|
||||||
<td class="text-right">Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
<tfoot class="border-top">
|
|
||||||
<tr>
|
|
||||||
<th colspan="2">Total:</th>
|
|
||||||
<th class="text-right">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</th>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<div class="card shadow">
|
|
||||||
<div class="card-body">
|
|
||||||
<button type="submit" class="btn btn-primary btn-block">
|
|
||||||
<i class="fas fa-save"></i> Update Order
|
|
||||||
</button>
|
|
||||||
<a href="{{ route('admin.transaksis.show', $transaksi->id) }}" class="btn btn-secondary btn-block">
|
|
||||||
<i class="fas fa-times"></i> Cancel
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,259 +1,471 @@
|
||||||
@extends('layouts.app')
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Order Details - Bibit Cabai Admin</title>
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f8f9fa; }
|
||||||
|
.admin-container { display: flex; min-height: 100vh; }
|
||||||
|
|
||||||
@section('title', 'Order Details')
|
/* ===== SIDEBAR ===== */
|
||||||
|
.sidebar {
|
||||||
|
width: 280px;
|
||||||
|
background: linear-gradient(180deg, #28a745, #20c997);
|
||||||
|
color: white;
|
||||||
|
min-height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 25px 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.sidebar-header h3 { font-size: 1.8rem; font-weight: bold; margin-bottom: 5px; }
|
||||||
|
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; }
|
||||||
|
.sidebar-menu { padding: 20px 0; }
|
||||||
|
.menu-item {
|
||||||
|
display: block;
|
||||||
|
padding: 15px 25px;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
.menu-item:hover, .menu-item.active {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-left-color: #fff;
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
.menu-item i { width: 20px; margin-right: 10px; }
|
||||||
|
|
||||||
@section('content')
|
/* ===== MAIN CONTENT ===== */
|
||||||
<div class="container-fluid">
|
.main-content { margin-left: 280px; flex: 1; min-height: 100vh; }
|
||||||
<!-- Page Header -->
|
.admin-header {
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
background: white;
|
||||||
<div>
|
padding: 20px 30px;
|
||||||
<h1 class="h3 mb-0 text-gray-800">Order Details</h1>
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
.content-area { padding: 30px; }
|
||||||
|
.breadcrumb { background: transparent; padding: 0; margin-bottom: 10px; }
|
||||||
|
.breadcrumb-item a { color: #28a745; text-decoration: none; }
|
||||||
|
|
||||||
|
/* ===== BACK BUTTON ===== */
|
||||||
|
.back-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px; right: 20px;
|
||||||
|
z-index: 1100;
|
||||||
|
background: linear-gradient(45deg, #dc3545, #c82333);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.back-button:hover {
|
||||||
|
background: linear-gradient(45deg, #c82333, #a71e2a);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== FADE IN ===== */
|
||||||
|
.fade-in { animation: fadeIn 0.3s ease-in-out; }
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CARD ===== */
|
||||||
|
.card { border: none; border-radius: 12px; }
|
||||||
|
.card-header {
|
||||||
|
background: #f8f9fa !important;
|
||||||
|
border-radius: 12px 12px 0 0 !important;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== BADGES ===== */
|
||||||
|
.badge-status {
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.badge-pending { background: #fff3cd; color: #856404; }
|
||||||
|
.badge-processing { background: #d1ecf1; color: #0c5460; }
|
||||||
|
.badge-shipped { background: #cce5ff; color: #004085; }
|
||||||
|
.badge-delivered { background: #d4edda; color: #155724; }
|
||||||
|
.badge-cancelled { background: #f8d7da; color: #721c24; }
|
||||||
|
.badge-paid { background: #d4edda; color: #155724; }
|
||||||
|
.badge-failed { background: #f8d7da; color: #721c24; }
|
||||||
|
|
||||||
|
/* ===== INFO TABLE ===== */
|
||||||
|
.info-table td { padding: 6px 0; border: none; vertical-align: top; }
|
||||||
|
.info-table td:first-child { font-weight: 600; width: 40%; color: #495057; }
|
||||||
|
|
||||||
|
/* ===== RESPONSIVE ===== */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar { width: 100%; position: relative; }
|
||||||
|
.main-content { margin-left: 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{{-- Back to Website --}}
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
|
||||||
|
{{-- ===== SIDEBAR ===== --}}
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item">
|
||||||
|
<i class="fas fa-seedling"></i>Kelola Produk
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item active">
|
||||||
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item">
|
||||||
|
<i class="fas fa-users"></i>Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="menu-item">
|
||||||
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
|
</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item">
|
||||||
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
|
</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{-- ===== MAIN CONTENT ===== --}}
|
||||||
|
<div class="main-content">
|
||||||
|
|
||||||
|
{{-- Header --}}
|
||||||
|
<div class="admin-header">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb bg-transparent p-0 mb-0">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
|
<li class="breadcrumb-item">
|
||||||
<li class="breadcrumb-item"><a href="{{ route('admin.orders') }}">Orders</a></li>
|
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{{ route('admin.orders') }}">Pesanan</a>
|
||||||
|
</li>
|
||||||
<li class="breadcrumb-item active">{{ $transaksi->invoice_number }}</li>
|
<li class="breadcrumb-item active">{{ $transaksi->invoice_number }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="btn-group" role="group">
|
<div>
|
||||||
<a href="{{ route('admin.orders') }}" class="btn btn-secondary">
|
<h2>Order Details</h2>
|
||||||
<i class="fas fa-arrow-left"></i> Back to Orders
|
<p class="mb-0 text-muted">{{ $transaksi->invoice_number }}</p>
|
||||||
</a>
|
|
||||||
<a href="{{ route('admin.transaksis.edit', $transaksi->id) }}" class="btn btn-warning">
|
|
||||||
<i class="fas fa-edit"></i> Edit
|
|
||||||
</a>
|
|
||||||
<a href="{{ route('admin.transaksis.print', $transaksi->id) }}" class="btn btn-info" target="_blank">
|
|
||||||
<i class="fas fa-print"></i> Print Invoice
|
|
||||||
</a>
|
|
||||||
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal">
|
|
||||||
<i class="fas fa-trash"></i> Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<!-- Order Information -->
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<!-- Invoice & Status Card -->
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3 d-flex justify-content-between align-items-center">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Order Information</h6>
|
|
||||||
<div>
|
|
||||||
@php
|
|
||||||
$statusClass = [
|
|
||||||
'pending' => 'warning',
|
|
||||||
'processing' => 'info',
|
|
||||||
'shipped' => 'primary',
|
|
||||||
'delivered' => 'success',
|
|
||||||
'cancelled' => 'danger'
|
|
||||||
][$transaksi->order_status] ?? 'secondary';
|
|
||||||
@endphp
|
|
||||||
<span class="badge badge-{{ $statusClass }} badge-lg">
|
|
||||||
{{ ucfirst($transaksi->order_status) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="d-flex gap-2">
|
||||||
<div class="row mb-3">
|
<a href="{{ route('admin.orders') }}" class="btn btn-secondary btn-sm">
|
||||||
<div class="col-md-6">
|
<i class="fas fa-arrow-left me-1"></i> Back to Orders
|
||||||
<h6 class="text-primary mb-3">Invoice Details</h6>
|
</a>
|
||||||
<table class="table table-sm table-borderless">
|
<a href="{{ route('admin.transaksis.edit', $transaksi->id) }}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="fas fa-edit me-1"></i> Edit
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.transaksis.print', $transaksi->id) }}" class="btn btn-info btn-sm" target="_blank">
|
||||||
|
<i class="fas fa-print me-1"></i> Print Invoice
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-danger btn-sm"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||||
|
<i class="fas fa-trash me-1"></i> Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Content --}}
|
||||||
|
<div class="content-area fade-in">
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
{{-- ===== KOLOM KIRI (8/12) ===== --}}
|
||||||
|
<div class="col-lg-8">
|
||||||
|
|
||||||
|
{{-- Order Information & Status --}}
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">
|
||||||
|
<i class="fas fa-file-alt me-2"></i>Order Information
|
||||||
|
</h6>
|
||||||
|
<span class="badge-status badge-{{ $transaksi->order_status }}">
|
||||||
|
{{ ucfirst($transaksi->order_status) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
{{-- Invoice Details --}}
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6 class="text-primary mb-3">Invoice Details</h6>
|
||||||
|
<table class="info-table table table-sm table-borderless">
|
||||||
|
<tr>
|
||||||
|
<td>Invoice Number:</td>
|
||||||
|
<td><strong>{{ $transaksi->invoice_number }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Order Date:</td>
|
||||||
|
<td>{{ $transaksi->created_at->format('d M Y, H:i') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Payment Status:</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge-status badge-{{ $transaksi->payment_status }}">
|
||||||
|
{{ ucfirst($transaksi->payment_status) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@if($transaksi->tracking_number)
|
||||||
|
<tr>
|
||||||
|
<td>Tracking Number:</td>
|
||||||
|
<td><code>{{ $transaksi->tracking_number }}</code></td>
|
||||||
|
</tr>
|
||||||
|
@endif
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Update Status --}}
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6 class="text-primary mb-3">Update Status</h6>
|
||||||
|
|
||||||
|
<form action="{{ route('admin.transaksis.update-status', $transaksi->id) }}" method="POST" class="mb-3">
|
||||||
|
@csrf
|
||||||
|
<label class="form-label fw-semibold small">Order Status</label>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<select name="order_status" class="form-select" id="order_status">
|
||||||
|
@foreach(['pending','processing','shipped','delivered','cancelled'] as $s)
|
||||||
|
<option value="{{ $s }}" {{ $transaksi->order_status == $s ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst($s) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form action="{{ route('admin.transaksis.update-payment-status', $transaksi->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<label class="form-label fw-semibold small">Payment Status</label>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<select name="payment_status" class="form-select" id="payment_status">
|
||||||
|
@foreach(['pending','paid','failed'] as $p)
|
||||||
|
<option value="{{ $p }}" {{ $transaksi->payment_status == $p ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst($p) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="fas fa-save"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Order Items --}}
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">
|
||||||
|
<i class="fas fa-box me-2"></i>Order Items
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Product</th>
|
||||||
|
<th class="text-center">Price</th>
|
||||||
|
<th class="text-center">Qty</th>
|
||||||
|
<th class="text-end">Subtotal</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($transaksi->details as $index => $detail)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $index + 1 }}</td>
|
||||||
|
<td><strong>{{ $detail->product->name ?? 'Product Not Found' }}</strong></td>
|
||||||
|
<td class="text-center">Rp {{ number_format($detail->price, 0, ',', '.') }}</td>
|
||||||
|
<td class="text-center">{{ $detail->quantity }}</td>
|
||||||
|
<td class="text-end">Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th colspan="4" class="text-end">Total Amount:</th>
|
||||||
|
<th class="text-end text-success">
|
||||||
|
Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{-- Akhir col-lg-8 --}}
|
||||||
|
|
||||||
|
{{-- ===== KOLOM KANAN (4/12) ===== --}}
|
||||||
|
<div class="col-lg-4">
|
||||||
|
|
||||||
|
{{-- Customer Information --}}
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">
|
||||||
|
<i class="fas fa-user me-2"></i>Customer Information
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="info-table table table-sm table-borderless">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="40%"><strong>Invoice Number:</strong></td>
|
<td><i class="fas fa-user text-success me-1"></i> Name:</td>
|
||||||
<td>{{ $transaksi->invoice_number }}</td>
|
<td>{{ $transaksi->customer_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Order Date:</strong></td>
|
<td><i class="fas fa-phone text-success me-1"></i> Phone:</td>
|
||||||
<td>{{ $transaksi->created_at->format('d M Y, H:i') }}</td>
|
<td>{{ $transaksi->customer_phone }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Payment Status:</strong></td>
|
<td><i class="fas fa-envelope text-success me-1"></i> Email:</td>
|
||||||
<td>
|
<td>{{ $transaksi->customer_email ?? '-' }}</td>
|
||||||
@php
|
|
||||||
$paymentClass = [
|
|
||||||
'pending' => 'warning',
|
|
||||||
'paid' => 'success',
|
|
||||||
'failed' => 'danger'
|
|
||||||
][$transaksi->payment_status] ?? 'secondary';
|
|
||||||
@endphp
|
|
||||||
<span class="badge badge-{{ $paymentClass }}">
|
|
||||||
{{ ucfirst($transaksi->payment_status) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
</div>
|
||||||
<h6 class="text-primary mb-3">Update Status</h6>
|
|
||||||
<form action="{{ route('admin.transaksis.update-status', $transaksi->id) }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="order_status">Order Status</label>
|
|
||||||
<select name="order_status" id="order_status" class="form-control">
|
|
||||||
<option value="pending" {{ $transaksi->order_status == 'pending' ? 'selected' : '' }}>Pending</option>
|
|
||||||
<option value="processing" {{ $transaksi->order_status == 'processing' ? 'selected' : '' }}>Processing</option>
|
|
||||||
<option value="shipped" {{ $transaksi->order_status == 'shipped' ? 'selected' : '' }}>Shipped</option>
|
|
||||||
<option value="delivered" {{ $transaksi->order_status == 'delivered' ? 'selected' : '' }}>Delivered</option>
|
|
||||||
<option value="cancelled" {{ $transaksi->order_status == 'cancelled' ? 'selected' : '' }}>Cancelled</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary btn-sm">
|
|
||||||
<i class="fas fa-save"></i> Update Order Status
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form action="{{ route('admin.transaksis.update-payment-status', $transaksi->id) }}" method="POST" class="mt-3">
|
{{-- Shipping Information --}}
|
||||||
@csrf
|
<div class="card shadow mb-4">
|
||||||
<div class="form-group">
|
<div class="card-header py-3">
|
||||||
<label for="payment_status">Payment Status</label>
|
<h6 class="m-0 fw-bold text-primary">
|
||||||
<select name="payment_status" id="payment_status" class="form-control">
|
<i class="fas fa-truck me-2"></i>Shipping Information
|
||||||
<option value="pending" {{ $transaksi->payment_status == 'pending' ? 'selected' : '' }}>Pending</option>
|
</h6>
|
||||||
<option value="paid" {{ $transaksi->payment_status == 'paid' ? 'selected' : '' }}>Paid</option>
|
</div>
|
||||||
<option value="failed" {{ $transaksi->payment_status == 'failed' ? 'selected' : '' }}>Failed</option>
|
<div class="card-body">
|
||||||
</select>
|
<p class="fw-semibold small text-muted mb-1">Shipping Address</p>
|
||||||
</div>
|
<address class="mb-3" style="line-height:1.7;">
|
||||||
<button type="submit" class="btn btn-success btn-sm">
|
{{ $transaksi->shipping_address }}<br>
|
||||||
<i class="fas fa-save"></i> Update Payment Status
|
{{ $transaksi->city }}, {{ $transaksi->province }}<br>
|
||||||
</button>
|
{{ $transaksi->postal_code }}
|
||||||
</form>
|
</address>
|
||||||
|
|
||||||
|
@if($transaksi->shipping_method)
|
||||||
|
<p class="fw-semibold small text-muted mb-1">Shipping Method</p>
|
||||||
|
<p class="mb-3">{{ $transaksi->shipping_method }}</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($transaksi->tracking_number)
|
||||||
|
<p class="fw-semibold small text-muted mb-1">Tracking Number</p>
|
||||||
|
<p class="mb-0"><code class="bg-light px-2 py-1 rounded">{{ $transaksi->tracking_number }}</code></p>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Order Items -->
|
{{-- Notes --}}
|
||||||
<div class="card shadow mb-4">
|
@if($transaksi->notes)
|
||||||
<div class="card-header py-3">
|
<div class="card shadow mb-4">
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Order Items</h6>
|
<div class="card-header py-3">
|
||||||
</div>
|
<h6 class="m-0 fw-bold text-primary">
|
||||||
<div class="card-body">
|
<i class="fas fa-sticky-note me-2"></i>Notes
|
||||||
<div class="table-responsive">
|
</h6>
|
||||||
<table class="table table-bordered">
|
</div>
|
||||||
<thead class="thead-light">
|
<div class="card-body">
|
||||||
<tr>
|
<p class="mb-0">{{ $transaksi->notes }}</p>
|
||||||
<th>#</th>
|
</div>
|
||||||
<th>Product</th>
|
|
||||||
<th>Price</th>
|
|
||||||
<th>Quantity</th>
|
|
||||||
<th>Subtotal</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($transaksi->details as $index => $detail)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $index + 1 }}</td>
|
|
||||||
<td>
|
|
||||||
<strong>{{ $detail->product->name ?? 'Product Not Found' }}</strong>
|
|
||||||
</td>
|
|
||||||
<td>Rp {{ number_format($detail->price, 0, ',', '.') }}</td>
|
|
||||||
<td>{{ $detail->quantity }}</td>
|
|
||||||
<td>Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<th colspan="4" class="text-right">Total Amount:</th>
|
|
||||||
<th>Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</th>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Customer & Shipping Info -->
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<!-- Customer Information -->
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Customer Information</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-sm table-borderless">
|
|
||||||
<tr>
|
|
||||||
<td><i class="fas fa-user text-primary"></i></td>
|
|
||||||
<td><strong>Name:</strong></td>
|
|
||||||
<td>{{ $transaksi->customer_name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><i class="fas fa-phone text-primary"></i></td>
|
|
||||||
<td><strong>Phone:</strong></td>
|
|
||||||
<td>{{ $transaksi->customer_phone }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><i class="fas fa-envelope text-primary"></i></td>
|
|
||||||
<td><strong>Email:</strong></td>
|
|
||||||
<td>{{ $transaksi->customer_email ?? '-' }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Shipping Information -->
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Shipping Information</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="mb-2"><strong>Shipping Address:</strong></p>
|
|
||||||
<address class="mb-3">
|
|
||||||
{{ $transaksi->shipping_address }}<br>
|
|
||||||
{{ $transaksi->city }}, {{ $transaksi->province }}<br>
|
|
||||||
{{ $transaksi->postal_code }}
|
|
||||||
</address>
|
|
||||||
|
|
||||||
@if($transaksi->shipping_method)
|
|
||||||
<p class="mb-1"><strong>Shipping Method:</strong></p>
|
|
||||||
<p>{{ $transaksi->shipping_method }}</p>
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($transaksi->tracking_number)
|
|
||||||
<p class="mb-1"><strong>Tracking Number:</strong></p>
|
|
||||||
<p><code>{{ $transaksi->tracking_number }}</code></p>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{-- Akhir col-lg-4 --}}
|
||||||
|
|
||||||
<!-- Additional Notes -->
|
|
||||||
@if($transaksi->notes)
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Notes</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="mb-0">{{ $transaksi->notes }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
{{-- Akhir content-area --}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{-- Akhir main-content --}}
|
||||||
|
|
||||||
<!-- Delete Confirmation Modal -->
|
</div>
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
{{-- Akhir admin-container --}}
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
|
{{-- ===== DELETE MODAL ===== --}}
|
||||||
|
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header bg-danger text-white">
|
<div class="modal-header bg-danger text-white">
|
||||||
<h5 class="modal-title" id="deleteModalLabel">Confirm Delete</h5>
|
<h5 class="modal-title" id="deleteModalLabel">
|
||||||
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
|
<i class="fas fa-exclamation-triangle me-2"></i>Confirm Delete
|
||||||
<span aria-hidden="true">×</span>
|
</h5>
|
||||||
</button>
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Are you sure you want to delete this order?</p>
|
<p>Are you sure you want to delete this order?</p>
|
||||||
<p><strong>Invoice: {{ $transaksi->invoice_number }}</strong></p>
|
<p><strong>Invoice: {{ $transaksi->invoice_number }}</strong></p>
|
||||||
<p class="text-danger"><i class="fas fa-exclamation-triangle"></i> This action cannot be undone!</p>
|
<p class="text-danger mb-0">
|
||||||
|
<i class="fas fa-exclamation-triangle me-1"></i> This action cannot be undone!
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
<form action="{{ route('admin.transaksis.destroy', $transaksi->id) }}" method="POST" style="display: inline;">
|
<form action="{{ route('admin.transaksis.destroy', $transaksi->id) }}" method="POST" class="d-inline">
|
||||||
@csrf
|
@csrf
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
<button type="submit" class="btn btn-danger">
|
<button type="submit" class="btn btn-danger">
|
||||||
<i class="fas fa-trash"></i> Delete Order
|
<i class="fas fa-trash me-1"></i> Delete Order
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -261,37 +473,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@push('styles')
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<style>
|
|
||||||
.badge-lg {
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
address {
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-item + .breadcrumb-item::before {
|
|
||||||
content: "›";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script>
|
<script>
|
||||||
// Auto-submit form when status changes
|
// Auto-confirm saat status berubah
|
||||||
document.getElementById('order_status').addEventListener('change', function() {
|
document.getElementById('order_status').addEventListener('change', function () {
|
||||||
if(confirm('Update order status?')) {
|
if (confirm('Update order status to "' + this.value + '"?')) {
|
||||||
this.form.submit();
|
this.form.submit();
|
||||||
|
} else {
|
||||||
|
this.form.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('payment_status').addEventListener('change', function() {
|
document.getElementById('payment_status').addEventListener('change', function () {
|
||||||
if(confirm('Update payment status?')) {
|
if (confirm('Update payment status to "' + this.value + '"?')) {
|
||||||
this.form.submit();
|
this.form.submit();
|
||||||
|
} else {
|
||||||
|
this.form.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
</body>
|
||||||
@endsection
|
</html>
|
||||||
|
|
@ -4,488 +4,485 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<title>Kelola Pengguna - Admin</title>
|
<title>Kelola Pengguna - Bibit Cabai Admin</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Font Awesome -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-container {
|
||||||
|
display: flex;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
/* Sidebar */
|
||||||
max-width: 1400px;
|
.sidebar {
|
||||||
margin: 0 auto;
|
width: 280px;
|
||||||
|
background: linear-gradient(180deg, #28a745, #20c997);
|
||||||
|
color: white;
|
||||||
|
min-height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.sidebar-header {
|
||||||
background: white;
|
padding: 25px 20px;
|
||||||
padding: 25px 30px;
|
text-align: center;
|
||||||
border-radius: 10px;
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
margin-bottom: 25px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header h1 {
|
.sidebar-header h3 {
|
||||||
color: #333;
|
font-size: 1.8rem;
|
||||||
font-size: 28px;
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-actions {
|
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; }
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
.sidebar-menu { padding: 20px 0; }
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
.menu-item {
|
||||||
border-radius: 5px;
|
display: block;
|
||||||
cursor: pointer;
|
padding: 15px 25px;
|
||||||
font-size: 14px;
|
color: white;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
display: inline-block;
|
border-left: 4px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.menu-item:hover, .menu-item.active {
|
||||||
background: #667eea;
|
background: rgba(255,255,255,0.2);
|
||||||
color: white;
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-left-color: #fff;
|
||||||
|
transform: translateX(5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.menu-item i { width: 20px; margin-right: 10px; }
|
||||||
background: #5568d3;
|
|
||||||
|
/* Main Content */
|
||||||
|
.main-content {
|
||||||
|
margin-left: 280px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-danger {
|
.admin-header {
|
||||||
background: #e74c3c;
|
background: white;
|
||||||
color: white;
|
padding: 20px 30px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-danger:hover {
|
.content-area { padding: 30px; }
|
||||||
background: #c0392b;
|
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
.breadcrumb { background: transparent; padding: 0; margin-bottom: 10px; }
|
||||||
padding: 15px 20px;
|
.breadcrumb-item a { color: #28a745; text-decoration: none; }
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-success {
|
/* Stats Cards */
|
||||||
background: #d4edda;
|
.stats-grid {
|
||||||
color: #155724;
|
|
||||||
border: 1px solid #c3e6cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
background: #f8d7da;
|
|
||||||
color: #721c24;
|
|
||||||
border: 1px solid #f5c6cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
background: white;
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card h3 {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card .number {
|
|
||||||
color: #667eea;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-left: 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.primary { border-left-color: #007bff; }
|
||||||
|
.stat-card.warning { border-left-color: #ffc107; }
|
||||||
|
.stat-card.info { border-left-color: #17a2b8; }
|
||||||
|
.stat-card.success { border-left-color: #28a745; }
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.primary .stat-label { color: #007bff; }
|
||||||
|
.stat-card.warning .stat-label { color: #ffc107; }
|
||||||
|
.stat-card.info .stat-label { color: #17a2b8; }
|
||||||
|
.stat-card.success .stat-label { color: #28a745; }
|
||||||
|
|
||||||
|
.stat-value { font-size: 1.5rem; font-weight: 700; color: #333; }
|
||||||
|
.stat-icon { font-size: 2rem; color: #dee2e6; }
|
||||||
|
|
||||||
|
/* Section Header */
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title { font-size: 1.3rem; font-weight: 600; color: #333; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th, .data-table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table tbody tr:hover { background-color: #f8f9fc; }
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.badge-role {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-admin { background: #cce5ff; color: #004085; }
|
||||||
|
.badge-user { background: #d4edda; color: #155724; }
|
||||||
|
.badge-verified { background: #d4edda; color: #155724; }
|
||||||
|
.badge-unverified { background: #e2e3e5; color: #383d41; }
|
||||||
|
|
||||||
|
/* Action Buttons */
|
||||||
|
.btn-action {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar input {
|
.btn-action:hover {
|
||||||
flex: 1;
|
transform: translateY(-1px);
|
||||||
padding: 12px 15px;
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
border: 2px solid #e0e0e0;
|
text-decoration: none;
|
||||||
border-radius: 5px;
|
color: inherit;
|
||||||
font-size: 14px;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar input:focus {
|
.btn-view { background: #17a2b8; color: white; }
|
||||||
outline: none;
|
.btn-edit { background: #ffc107; color: #212529; }
|
||||||
border-color: #667eea;
|
.btn-delete { background: #dc3545; color: white; }
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar button {
|
/* Back Button */
|
||||||
background: #667eea;
|
.back-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1100;
|
||||||
|
background: linear-gradient(45deg, #dc3545, #c82333);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 12px 25px;
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
padding: 12px 20px;
|
||||||
cursor: pointer;
|
border-radius: 25px;
|
||||||
font-size: 14px;
|
|
||||||
transition: background 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar button:hover {
|
|
||||||
background: #5568d3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-responsive {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead {
|
|
||||||
background: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: 15px;
|
|
||||||
text-align: left;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
|
||||||
border-bottom: 2px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 15px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover {
|
|
||||||
background: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 5px 12px;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-admin {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-user {
|
|
||||||
background: #27ae60;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-verified {
|
|
||||||
background: #2ecc71;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-unverified {
|
|
||||||
background: #95a5a6;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
padding: 6px 12px;
|
|
||||||
margin: 0 3px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-edit {
|
.back-button:hover {
|
||||||
background: #3498db;
|
background: linear-gradient(45deg, #c82333, #a71e2a);
|
||||||
|
transform: translateY(-2px);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
|
||||||
|
|
||||||
.btn-edit:hover {
|
|
||||||
background: #2980b9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-delete {
|
|
||||||
background: #e74c3c;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-delete:hover {
|
|
||||||
background: #c0392b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-view {
|
|
||||||
background: #95a5a6;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-view:hover {
|
|
||||||
background: #7f8c8d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination a, .pagination span {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #667eea;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination a:hover {
|
.fade-in {
|
||||||
background: #667eea;
|
animation: fadeIn 0.3s ease-in-out;
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination .active {
|
@keyframes fadeIn {
|
||||||
background: #667eea;
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
color: white;
|
to { opacity: 1; transform: translateY(0); }
|
||||||
border-color: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state i {
|
|
||||||
font-size: 48px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.header {
|
.sidebar { width: 100%; position: relative; }
|
||||||
flex-direction: column;
|
.main-content { margin-left: 0; }
|
||||||
gap: 15px;
|
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
padding: 10px 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<!-- Back Button -->
|
||||||
<!-- Header -->
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
<div class="header">
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
<div>
|
</a>
|
||||||
<h1>👥 Kelola Pengguna</h1>
|
|
||||||
<p style="color: #666; margin-top: 5px;">Dashboard Admin - TA Bibit Cabai</p>
|
|
||||||
</div>
|
|
||||||
<div class="header-actions">
|
|
||||||
<a href="{{ route('admin.users.create') }}" class="btn btn-primary">+ Tambah Pengguna</a>
|
|
||||||
<a href="{{ route('admin.dashboard') }}" class="btn btn-primary">← Dashboard</a>
|
|
||||||
<form method="POST" action="{{ route('logout') }}" style="display: inline;">
|
|
||||||
@csrf
|
|
||||||
<button type="submit" class="btn btn-danger">Logout</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Alert Messages -->
|
<div class="admin-container">
|
||||||
@if(session('success'))
|
<!-- Sidebar -->
|
||||||
<div class="alert alert-success">
|
<nav class="sidebar">
|
||||||
✓ {{ session('success') }}
|
<div class="sidebar-header">
|
||||||
</div>
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
@endif
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small>
|
||||||
@if(session('error'))
|
|
||||||
<div class="alert alert-error">
|
|
||||||
✗ {{ session('error') }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<!-- Stats Cards -->
|
|
||||||
<div class="stats">
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>Total Pengguna</h3>
|
|
||||||
<div class="number">{{ $totalUsers ?? 0 }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="sidebar-menu">
|
||||||
<h3>Admin</h3>
|
<a href="{{ route('admin.dashboard') }}" class="menu-item">
|
||||||
<div class="number">{{ $totalAdmins ?? 0 }}</div>
|
<i class="fas fa-tachometer-alt"></i>Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item">
|
||||||
|
<i class="fas fa-seedling"></i>Kelola Produk
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item">
|
||||||
|
<i class="fas fa-shopping-cart"></i>Pesanan
|
||||||
|
</a>
|
||||||
|
{{-- ↓ TAMBAHKAN DI SINI ↓ --}}
|
||||||
|
<a href="{{ route('admin.cancellations') }}" class="menu-item">
|
||||||
|
<i class="fas fa-times-circle"></i>Pengajuan Batal
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item active">
|
||||||
|
<i class="fas fa-users"></i>Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.laporan') }}" class="menu-item">
|
||||||
|
<i class="fas fa-chart-line"></i>Laporan
|
||||||
|
</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item">
|
||||||
|
<i class="fas fa-cog"></i>Pengaturan
|
||||||
|
</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
</nav>
|
||||||
<h3>Email Terverifikasi</h3>
|
|
||||||
<div class="number">{{ $verifiedUsers ?? 0 }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>Pengguna Aktif (30 Hari)</h3>
|
|
||||||
<div class="number">{{ $activeUsers ?? 0 }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="content">
|
<div class="main-content">
|
||||||
<!-- Search Bar -->
|
<div class="admin-header">
|
||||||
<div class="search-bar">
|
<nav aria-label="breadcrumb">
|
||||||
<input type="text" id="searchInput" placeholder="🔍 Cari pengguna berdasarkan nama, email, atau nomor telepon...">
|
<ol class="breadcrumb">
|
||||||
<button onclick="searchUsers()">Cari</button>
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{{ route('admin.dashboard') }}">
|
||||||
|
<i class="fas fa-home"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Pengguna</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2>Kelola Pengguna</h2>
|
||||||
|
<p class="mb-0">Kelola semua pengguna terdaftar di sini!</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Users Table -->
|
<div class="content-area fade-in">
|
||||||
@if(isset($users) && $users->count() > 0)
|
|
||||||
<div class="table-responsive">
|
@if(session('success'))
|
||||||
<table id="usersTable">
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
<thead>
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
<tr>
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
<th>ID</th>
|
</div>
|
||||||
<th>Nama</th>
|
@endif
|
||||||
<th>Email</th>
|
|
||||||
<th>Telepon</th>
|
@if(session('error'))
|
||||||
<th>Alamat</th>
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<th>Status</th>
|
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||||
<th>Verifikasi</th>
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
<th>Aksi</th>
|
</div>
|
||||||
</tr>
|
@endif
|
||||||
</thead>
|
|
||||||
<tbody>
|
<!-- Stats Cards -->
|
||||||
@foreach($users as $user)
|
<div class="stats-grid">
|
||||||
<tr>
|
<div class="stat-card primary">
|
||||||
<td>{{ $user->id }}</td>
|
<div>
|
||||||
<td><strong>{{ $user->name }}</strong></td>
|
<div class="stat-label">Total Pengguna</div>
|
||||||
<td>{{ $user->email }}</td>
|
<div class="stat-value">{{ $totalUsers ?? 0 }}</div>
|
||||||
<td>{{ $user->phone ?? '-' }}</td>
|
</div>
|
||||||
<td>{{ Str::limit($user->address ?? '-', 30) }}</td>
|
<div class="stat-icon"><i class="fas fa-users"></i></div>
|
||||||
<td>
|
</div>
|
||||||
@if($user->is_admin == 1)
|
<div class="stat-card warning">
|
||||||
<span class="badge badge-admin">Admin</span>
|
<div>
|
||||||
@else
|
<div class="stat-label">Admin</div>
|
||||||
<span class="badge badge-user">User</span>
|
<div class="stat-value">{{ $totalAdmins ?? 0 }}</div>
|
||||||
@endif
|
</div>
|
||||||
</td>
|
<div class="stat-icon"><i class="fas fa-user-shield"></i></div>
|
||||||
<td>
|
</div>
|
||||||
@if($user->email_verified_at)
|
<div class="stat-card success">
|
||||||
<span class="badge badge-verified">✓ Terverifikasi</span>
|
<div>
|
||||||
@else
|
<div class="stat-label">Email Terverifikasi</div>
|
||||||
<span class="badge badge-unverified">Belum Verifikasi</span>
|
<div class="stat-value">{{ $verifiedUsers ?? 0 }}</div>
|
||||||
@endif
|
</div>
|
||||||
</td>
|
<div class="stat-icon"><i class="fas fa-check-circle"></i></div>
|
||||||
<td>
|
</div>
|
||||||
<a href="{{ route('admin.users.show', $user) }}" class="action-btn btn-view">Detail</a>
|
<div class="stat-card info">
|
||||||
<a href="{{ route('admin.users.edit', $user) }}" class="action-btn btn-edit">Edit</a>
|
<div>
|
||||||
<form method="POST" action="{{ route('admin.users.destroy', $user) }}" style="display: inline;" onsubmit="return confirm('Apakah Anda yakin ingin menghapus pengguna ini?');">
|
<div class="stat-label">Aktif (30 Hari)</div>
|
||||||
@csrf
|
<div class="stat-value">{{ $activeUsers ?? 0 }}</div>
|
||||||
@method('DELETE')
|
</div>
|
||||||
<button type="submit" class="action-btn btn-delete">Hapus</button>
|
<div class="stat-icon"><i class="fas fa-user-clock"></i></div>
|
||||||
</form>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
@endforeach
|
<!-- Users Table -->
|
||||||
</tbody>
|
<div class="page-header">
|
||||||
</table>
|
<div class="section-header">
|
||||||
</div>
|
<h3 class="section-title">
|
||||||
|
<i class="fas fa-users text-success me-2"></i>
|
||||||
|
Daftar Pengguna ({{ $totalUsers ?? 0 }})
|
||||||
|
</h3>
|
||||||
|
<a href="{{ route('admin.users.create') }}" class="btn btn-primary btn-sm">
|
||||||
|
<i class="fas fa-plus me-1"></i>Tambah Pengguna
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="input-group" style="max-width: 400px;">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
<input type="text" class="form-control" id="searchInput"
|
||||||
|
placeholder="Cari nama, email, atau telepon...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(isset($users) && $users->count() > 0)
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="data-table" id="usersTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Nama</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Telepon</th>
|
||||||
|
<th>Alamat</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Verifikasi</th>
|
||||||
|
<th>Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($users as $user)
|
||||||
|
<tr>
|
||||||
|
<td>{{ ($users->currentPage() - 1) * $users->perPage() + $loop->iteration }}</td>
|
||||||
|
<td><strong>{{ $user->name }}</strong></td>
|
||||||
|
<td>{{ $user->email }}</td>
|
||||||
|
<td>{{ $user->phone ?? '-' }}</td>
|
||||||
|
<td><small>{{ \Illuminate\Support\Str::limit($user->address ?? '-', 30) }}</small></td>
|
||||||
|
<td>
|
||||||
|
@if($user->is_admin == 1)
|
||||||
|
<span class="badge-role badge-admin">Admin</span>
|
||||||
|
@else
|
||||||
|
<span class="badge-role badge-user">User</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($user->email_verified_at)
|
||||||
|
<span class="badge-role badge-verified">
|
||||||
|
<i class="fas fa-check me-1"></i>Terverifikasi
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<span class="badge-role badge-unverified">Belum Verifikasi</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-1">
|
||||||
|
<a href="{{ route('admin.users.show', $user) }}"
|
||||||
|
class="btn-action btn-view" title="Detail">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users.edit', $user) }}"
|
||||||
|
class="btn-action btn-edit" title="Edit">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
||||||
|
style="display: inline;"
|
||||||
|
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn-action btn-delete" title="Hapus">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||||
|
<small class="text-muted">
|
||||||
|
Menampilkan {{ $users->firstItem() }} sampai {{ $users->lastItem() }}
|
||||||
|
dari {{ $users->total() }} pengguna
|
||||||
|
</small>
|
||||||
|
<div>{{ $users->links() }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@else
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-users fa-3x text-muted mb-3 d-block"></i>
|
||||||
|
<h5 class="text-muted">Belum ada pengguna terdaftar</h5>
|
||||||
|
<p class="text-muted">Mulai dengan menambahkan pengguna baru.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
|
||||||
@if(isset($users))
|
|
||||||
<div class="pagination">
|
|
||||||
{{ $users->links() }}
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
@else
|
|
||||||
<div class="empty-state">
|
|
||||||
<div>📭</div>
|
|
||||||
<h3>Tidak ada data pengguna</h3>
|
|
||||||
<p>Belum ada pengguna terdaftar dalam sistem</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Search function
|
// Live search
|
||||||
function searchUsers() {
|
document.getElementById('searchInput').addEventListener('keyup', function() {
|
||||||
const input = document.getElementById('searchInput');
|
const filter = this.value.toLowerCase();
|
||||||
const filter = input.value.toLowerCase();
|
const rows = document.querySelectorAll('#usersTable tbody tr');
|
||||||
const table = document.getElementById('usersTable');
|
rows.forEach(row => {
|
||||||
|
const text = row.textContent.toLowerCase();
|
||||||
if (!table) return;
|
row.style.display = text.includes(filter) ? '' : 'none';
|
||||||
|
});
|
||||||
const tr = table.getElementsByTagName('tr');
|
});
|
||||||
|
|
||||||
for (let i = 1; i < tr.length; i++) {
|
|
||||||
let td = tr[i].getElementsByTagName('td');
|
|
||||||
let found = false;
|
|
||||||
|
|
||||||
for (let j = 0; j < td.length; j++) {
|
|
||||||
if (td[j]) {
|
|
||||||
let txtValue = td[j].textContent || td[j].innerText;
|
|
||||||
if (txtValue.toLowerCase().indexOf(filter) > -1) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tr[i].style.display = found ? '' : 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search on input
|
|
||||||
document.getElementById('searchInput')?.addEventListener('keyup', searchUsers);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Tambah Pengguna - Bibit Cabai Admin</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
|
body { font-family:'Segoe UI',sans-serif; background:#f8f9fa; }
|
||||||
|
.admin-container { display:flex; min-height:100vh; }
|
||||||
|
.sidebar {
|
||||||
|
width:280px; background:linear-gradient(180deg,#28a745,#20c997);
|
||||||
|
color:white; min-height:100vh; position:fixed; top:0; left:0; z-index:1000;
|
||||||
|
}
|
||||||
|
.sidebar-header { padding:25px 20px; text-align:center; border-bottom:1px solid rgba(255,255,255,0.1); }
|
||||||
|
.sidebar-header h3 { font-size:1.8rem; font-weight:bold; margin-bottom:5px; }
|
||||||
|
.sidebar-menu { padding:20px 0; }
|
||||||
|
.menu-item {
|
||||||
|
display:block; padding:15px 25px; color:white; text-decoration:none;
|
||||||
|
transition:all 0.3s; border-left:4px solid transparent;
|
||||||
|
}
|
||||||
|
.menu-item:hover, .menu-item.active {
|
||||||
|
background:rgba(255,255,255,0.2); color:white;
|
||||||
|
border-left-color:#fff; transform:translateX(5px);
|
||||||
|
}
|
||||||
|
.menu-item i { width:20px; margin-right:10px; }
|
||||||
|
.main-content { margin-left:280px; flex:1; }
|
||||||
|
.admin-header { background:white; padding:20px 30px; box-shadow:0 2px 4px rgba(0,0,0,0.1); }
|
||||||
|
.content-area { padding:30px; }
|
||||||
|
.form-card { background:white; border-radius:12px; padding:35px; box-shadow:0 4px 6px rgba(0,0,0,0.05); max-width:700px; }
|
||||||
|
.form-label { font-weight:600; color:#495057; }
|
||||||
|
.form-control:focus { border-color:#28a745; box-shadow:0 0 0 0.2rem rgba(40,167,69,0.25); }
|
||||||
|
.btn-save { background:linear-gradient(45deg,#28a745,#20c997); border:none; color:white; padding:12px 30px; border-radius:8px; font-weight:600; }
|
||||||
|
.btn-save:hover { opacity:0.9; color:white; }
|
||||||
|
.breadcrumb { background:transparent; padding:0; margin-bottom:10px; }
|
||||||
|
.breadcrumb-item a { color:#28a745; text-decoration:none; }
|
||||||
|
.back-button {
|
||||||
|
position:fixed; top:20px; right:20px; z-index:1100;
|
||||||
|
background:linear-gradient(45deg,#dc3545,#c82333);
|
||||||
|
color:white; border:none; padding:12px 20px;
|
||||||
|
border-radius:25px; font-weight:600; text-decoration:none; transition:all 0.3s;
|
||||||
|
}
|
||||||
|
.back-button:hover { transform:translateY(-2px); color:white; }
|
||||||
|
.section-divider { border-top:2px solid #f8f9fa; margin:25px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item"><i class="fas fa-tachometer-alt"></i>Dashboard</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item"><i class="fas fa-seedling"></i>Kelola Produk</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item"><i class="fas fa-shopping-cart"></i>Pesanan</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item active"><i class="fas fa-users"></i>Pengguna</a>
|
||||||
|
<a href="{{ route('admin.reports') }}" class="menu-item"><i class="fas fa-chart-line"></i>Laporan</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"><i class="fas fa-cog"></i>Pengaturan</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="admin-header">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin.users') }}">Pengguna</a></li>
|
||||||
|
<li class="breadcrumb-item active">Tambah Pengguna</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2>Tambah Pengguna Baru</h2>
|
||||||
|
<p class="mb-0">Isi form berikut untuk menambah pengguna baru.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area">
|
||||||
|
<div class="form-card">
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<ul class="mb-0">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<li>{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.users.store') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
{{-- Informasi Dasar --}}
|
||||||
|
<h5 class="mb-3 text-success"><i class="fas fa-user me-2"></i>Informasi Dasar</h5>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Lengkap <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror"
|
||||||
|
value="{{ old('name') }}" placeholder="Masukkan nama lengkap" required>
|
||||||
|
@error('name')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email <span class="text-danger">*</span></label>
|
||||||
|
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror"
|
||||||
|
value="{{ old('email') }}" placeholder="contoh@email.com" required>
|
||||||
|
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Password <span class="text-danger">*</span></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" name="password" id="password"
|
||||||
|
class="form-control @error('password') is-invalid @enderror"
|
||||||
|
placeholder="Minimal 8 karakter" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('password', 'eye1')">
|
||||||
|
<i class="fas fa-eye" id="eye1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@error('password')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Konfirmasi Password <span class="text-danger">*</span></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" name="password_confirmation" id="password_confirmation"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Ulangi password" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('password_confirmation', 'eye2')">
|
||||||
|
<i class="fas fa-eye" id="eye2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-divider"></div>
|
||||||
|
<h5 class="mb-3 text-success"><i class="fas fa-address-card me-2"></i>Informasi Tambahan</h5>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nomor Telepon</label>
|
||||||
|
<input type="text" name="phone"
|
||||||
|
value="{{ old('phone', '+62') }}"
|
||||||
|
placeholder="+62812xxxxxxxx">
|
||||||
|
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Alamat</label>
|
||||||
|
<textarea name="address" class="form-control @error('address') is-invalid @enderror"
|
||||||
|
rows="4" style="min-height: 100px; resize: vertical;"
|
||||||
|
placeholder="Contoh: Jl. Mawar No. 10, RT 02/RW 03, Kelurahan Sumbersari, Jember">{{ old('address') }}</textarea>
|
||||||
|
@error('address')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-divider"></div>
|
||||||
|
<h5 class="mb-3 text-success"><i class="fas fa-shield-alt me-2"></i>Hak Akses</h5>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_admin" id="is_admin"
|
||||||
|
{{ old('is_admin') ? 'checked' : '' }}>
|
||||||
|
<label class="form-check-label" for="is_admin">
|
||||||
|
<strong>Jadikan Admin</strong>
|
||||||
|
<small class="text-muted d-block">Admin memiliki akses penuh ke dashboard.</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-save">
|
||||||
|
<i class="fas fa-save me-2"></i>Simpan Pengguna
|
||||||
|
</button>
|
||||||
|
<a href="{{ route('admin.users') }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-times me-2"></i>Batal
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function togglePassword(fieldId, iconId) {
|
||||||
|
const field = document.getElementById(fieldId);
|
||||||
|
const icon = document.getElementById(iconId);
|
||||||
|
if (field.type === 'password') {
|
||||||
|
field.type = 'text';
|
||||||
|
icon.classList.replace('fa-eye', 'fa-eye-slash');
|
||||||
|
} else {
|
||||||
|
field.type = 'password';
|
||||||
|
icon.classList.replace('fa-eye-slash', 'fa-eye');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,236 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Edit Pengguna - Bibit Cabai Admin</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
|
body { font-family:'Segoe UI',sans-serif; background:#f8f9fa; }
|
||||||
|
.admin-container { display:flex; min-height:100vh; }
|
||||||
|
.sidebar {
|
||||||
|
width:280px; background:linear-gradient(180deg,#28a745,#20c997);
|
||||||
|
color:white; min-height:100vh; position:fixed; top:0; left:0; z-index:1000;
|
||||||
|
}
|
||||||
|
.sidebar-header { padding:25px 20px; text-align:center; border-bottom:1px solid rgba(255,255,255,0.1); }
|
||||||
|
.sidebar-header h3 { font-size:1.8rem; font-weight:bold; margin-bottom:5px; }
|
||||||
|
.sidebar-menu { padding:20px 0; }
|
||||||
|
.menu-item {
|
||||||
|
display:block; padding:15px 25px; color:white; text-decoration:none;
|
||||||
|
transition:all 0.3s; border-left:4px solid transparent;
|
||||||
|
}
|
||||||
|
.menu-item:hover, .menu-item.active {
|
||||||
|
background:rgba(255,255,255,0.2); color:white;
|
||||||
|
border-left-color:#fff; transform:translateX(5px);
|
||||||
|
}
|
||||||
|
.menu-item i { width:20px; margin-right:10px; }
|
||||||
|
.main-content { margin-left:280px; flex:1; }
|
||||||
|
.admin-header { background:white; padding:20px 30px; box-shadow:0 2px 4px rgba(0,0,0,0.1); }
|
||||||
|
.content-area { padding:30px; }
|
||||||
|
.form-card { background:white; border-radius:12px; padding:35px; box-shadow:0 4px 6px rgba(0,0,0,0.05); max-width:700px; }
|
||||||
|
.form-label { font-weight:600; color:#495057; }
|
||||||
|
.form-control:focus { border-color:#28a745; box-shadow:0 0 0 0.2rem rgba(40,167,69,0.25); }
|
||||||
|
.btn-save { background:linear-gradient(45deg,#28a745,#20c997); border:none; color:white; padding:12px 30px; border-radius:8px; font-weight:600; }
|
||||||
|
.btn-save:hover { opacity:0.9; color:white; }
|
||||||
|
.breadcrumb { background:transparent; padding:0; margin-bottom:10px; }
|
||||||
|
.breadcrumb-item a { color:#28a745; text-decoration:none; }
|
||||||
|
.back-button {
|
||||||
|
position:fixed; top:20px; right:20px; z-index:1100;
|
||||||
|
background:linear-gradient(45deg,#dc3545,#c82333);
|
||||||
|
color:white; border:none; padding:12px 20px;
|
||||||
|
border-radius:25px; font-weight:600; text-decoration:none; transition:all 0.3s;
|
||||||
|
}
|
||||||
|
.back-button:hover { transform:translateY(-2px); color:white; }
|
||||||
|
.section-divider { border-top:2px solid #f8f9fa; margin:25px 0; }
|
||||||
|
.user-info-badge {
|
||||||
|
background:#f8f9fa; border-radius:8px; padding:12px 15px;
|
||||||
|
margin-bottom:20px; font-size:0.85rem; color:#6c757d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item"><i class="fas fa-tachometer-alt"></i>Dashboard</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item"><i class="fas fa-seedling"></i>Kelola Produk</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item"><i class="fas fa-shopping-cart"></i>Pesanan</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item active"><i class="fas fa-users"></i>Pengguna</a>
|
||||||
|
<a href="{{ route('admin.reports') }}" class="menu-item"><i class="fas fa-chart-line"></i>Laporan</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"><i class="fas fa-cog"></i>Pengaturan</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="admin-header">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin.users') }}">Pengguna</a></li>
|
||||||
|
<li class="breadcrumb-item active">Edit Pengguna</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2>Edit Pengguna</h2>
|
||||||
|
<p class="mb-0">Perbarui informasi pengguna <strong>{{ $user->name }}</strong>.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area">
|
||||||
|
<div class="form-card">
|
||||||
|
|
||||||
|
<div class="user-info-badge">
|
||||||
|
<i class="fas fa-info-circle me-2 text-info"></i>
|
||||||
|
ID Pengguna: <strong>#{{ $user->id }}</strong> |
|
||||||
|
Terdaftar: <strong>{{ $user->created_at->format('d M Y') }}</strong>
|
||||||
|
@if($user->email_verified_at)
|
||||||
|
| <span class="text-success"><i class="fas fa-check-circle"></i> Email Terverifikasi</span>
|
||||||
|
@else
|
||||||
|
| <span class="text-muted"><i class="fas fa-clock"></i> Belum Verifikasi</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<ul class="mb-0">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<li>{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.users.update', $user) }}">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
|
||||||
|
<h5 class="mb-3 text-success"><i class="fas fa-user me-2"></i>Informasi Dasar</h5>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Lengkap <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror"
|
||||||
|
value="{{ old('name', $user->name) }}" required>
|
||||||
|
@error('name')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email <span class="text-danger">*</span></label>
|
||||||
|
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror"
|
||||||
|
value="{{ old('email', $user->email) }}" required>
|
||||||
|
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Password Baru</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" name="password" id="password"
|
||||||
|
class="form-control @error('password') is-invalid @enderror"
|
||||||
|
placeholder="Minimal 8 karakter">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('password', 'eye1')">
|
||||||
|
<i class="fas fa-eye" id="eye1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@error('password')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Konfirmasi Password Baru</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" name="password_confirmation" id="password_confirmation"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Ulangi password baru">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('password_confirmation', 'eye2')">
|
||||||
|
<i class="fas fa-eye" id="eye2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-divider"></div>
|
||||||
|
<h5 class="mb-3 text-success"><i class="fas fa-address-card me-2"></i>Informasi Tambahan</h5>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nomor Telepon</label>
|
||||||
|
<input type="text" name="phone"
|
||||||
|
value="{{ old('phone', '+62') }}"
|
||||||
|
placeholder="+62812xxxxxxxx">
|
||||||
|
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Alamat</label>
|
||||||
|
<textarea name="address" class="form-control @error('address') is-invalid @enderror"
|
||||||
|
rows="4" style="min-height: 100px; resize: vertical;">{{ old('address', $user->address) }}</textarea>
|
||||||
|
@error('address')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-divider"></div>
|
||||||
|
<h5 class="mb-3 text-success"><i class="fas fa-shield-alt me-2"></i>Hak Akses</h5>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_admin" id="is_admin"
|
||||||
|
{{ old('is_admin', $user->is_admin) ? 'checked' : '' }}
|
||||||
|
{{ $user->id === auth()->id() ? 'disabled' : '' }}>
|
||||||
|
<label class="form-check-label" for="is_admin">
|
||||||
|
<strong>Jadikan Admin</strong>
|
||||||
|
@if($user->id === auth()->id())
|
||||||
|
<small class="text-danger d-block">Anda tidak dapat mengubah role akun sendiri.</small>
|
||||||
|
@else
|
||||||
|
<small class="text-muted d-block">Admin memiliki akses penuh ke dashboard.</small>
|
||||||
|
@endif
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-save">
|
||||||
|
<i class="fas fa-save me-2"></i>Simpan Perubahan
|
||||||
|
</button>
|
||||||
|
<a href="{{ route('admin.users') }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-times me-2"></i>Batal
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users.show', $user) }}" class="btn btn-outline-info ms-auto">
|
||||||
|
<i class="fas fa-eye me-2"></i>Lihat Detail
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function togglePassword(fieldId, iconId) {
|
||||||
|
const field = document.getElementById(fieldId);
|
||||||
|
const icon = document.getElementById(iconId);
|
||||||
|
if (field.type === 'password') {
|
||||||
|
field.type = 'text';
|
||||||
|
icon.classList.replace('fa-eye', 'fa-eye-slash');
|
||||||
|
} else {
|
||||||
|
field.type = 'password';
|
||||||
|
icon.classList.replace('fa-eye-slash', 'fa-eye');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Detail Pengguna - Bibit Cabai Admin</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
|
body { font-family:'Segoe UI',sans-serif; background:#f8f9fa; }
|
||||||
|
.admin-container { display:flex; min-height:100vh; }
|
||||||
|
.sidebar {
|
||||||
|
width:280px; background:linear-gradient(180deg,#28a745,#20c997);
|
||||||
|
color:white; min-height:100vh; position:fixed; top:0; left:0; z-index:1000;
|
||||||
|
}
|
||||||
|
.sidebar-header { padding:25px 20px; text-align:center; border-bottom:1px solid rgba(255,255,255,0.1); }
|
||||||
|
.sidebar-header h3 { font-size:1.8rem; font-weight:bold; margin-bottom:5px; }
|
||||||
|
.sidebar-menu { padding:20px 0; }
|
||||||
|
.menu-item {
|
||||||
|
display:block; padding:15px 25px; color:white; text-decoration:none;
|
||||||
|
transition:all 0.3s; border-left:4px solid transparent;
|
||||||
|
}
|
||||||
|
.menu-item:hover, .menu-item.active {
|
||||||
|
background:rgba(255,255,255,0.2); color:white;
|
||||||
|
border-left-color:#fff; transform:translateX(5px);
|
||||||
|
}
|
||||||
|
.menu-item i { width:20px; margin-right:10px; }
|
||||||
|
.main-content { margin-left:280px; flex:1; }
|
||||||
|
.admin-header { background:white; padding:20px 30px; box-shadow:0 2px 4px rgba(0,0,0,0.1); }
|
||||||
|
.content-area { padding:30px; }
|
||||||
|
.detail-card { background:white; border-radius:12px; padding:35px; box-shadow:0 4px 6px rgba(0,0,0,0.05); max-width:700px; }
|
||||||
|
.avatar-circle {
|
||||||
|
width:80px; height:80px; border-radius:50%;
|
||||||
|
background:linear-gradient(135deg,#28a745,#20c997);
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
font-size:2rem; color:white; font-weight:bold; margin-bottom:15px;
|
||||||
|
}
|
||||||
|
.info-row { display:flex; padding:14px 0; border-bottom:1px solid #f8f9fa; }
|
||||||
|
.info-row:last-child { border-bottom:none; }
|
||||||
|
.info-label { width:160px; font-weight:600; color:#6c757d; font-size:0.9rem; flex-shrink:0; }
|
||||||
|
.info-value { color:#333; flex:1; }
|
||||||
|
.badge-role { padding:5px 12px; border-radius:12px; font-size:0.8rem; font-weight:600; }
|
||||||
|
.badge-admin { background:#cce5ff; color:#004085; }
|
||||||
|
.badge-user { background:#d4edda; color:#155724; }
|
||||||
|
.badge-verified { background:#d4edda; color:#155724; }
|
||||||
|
.badge-unverified { background:#e2e3e5; color:#383d41; }
|
||||||
|
.breadcrumb { background:transparent; padding:0; margin-bottom:10px; }
|
||||||
|
.breadcrumb-item a { color:#28a745; text-decoration:none; }
|
||||||
|
.back-button {
|
||||||
|
position:fixed; top:20px; right:20px; z-index:1100;
|
||||||
|
background:linear-gradient(45deg,#dc3545,#c82333);
|
||||||
|
color:white; border:none; padding:12px 20px;
|
||||||
|
border-radius:25px; font-weight:600; text-decoration:none; transition:all 0.3s;
|
||||||
|
}
|
||||||
|
.back-button:hover { transform:translateY(-2px); color:white; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<a href="{{ route('home') }}" class="back-button">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>🌱 Bibit Cabai</h3>
|
||||||
|
<p>Admin Dashboard</p>
|
||||||
|
<small class="text-light">{{ Auth::user()->name ?? 'Admin' }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-menu">
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="menu-item"><i class="fas fa-tachometer-alt"></i>Dashboard</a>
|
||||||
|
<a href="{{ route('admin.products.index') }}" class="menu-item"><i class="fas fa-seedling"></i>Kelola Produk</a>
|
||||||
|
<a href="{{ route('admin.orders') }}" class="menu-item"><i class="fas fa-shopping-cart"></i>Pesanan</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="menu-item active"><i class="fas fa-users"></i>Pengguna</a>
|
||||||
|
<a href="{{ route('admin.reports') }}" class="menu-item"><i class="fas fa-chart-line"></i>Laporan</a>
|
||||||
|
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"><i class="fas fa-cog"></i>Pengaturan</a> -->
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<form method="POST" action="{{ route('admin.logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-light btn-sm w-100">
|
||||||
|
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="admin-header">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin.users') }}">Pengguna</a></li>
|
||||||
|
<li class="breadcrumb-item active">Detail Pengguna</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2>Detail Pengguna</h2>
|
||||||
|
<p class="mb-0">Informasi lengkap pengguna.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area">
|
||||||
|
<div class="detail-card">
|
||||||
|
|
||||||
|
{{-- Avatar & Nama --}}
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div class="avatar-circle mx-auto">
|
||||||
|
{{ strtoupper(substr($user->name, 0, 1)) }}
|
||||||
|
</div>
|
||||||
|
<h4 class="mb-1">{{ $user->name }}</h4>
|
||||||
|
@if($user->is_admin)
|
||||||
|
<span class="badge-role badge-admin"><i class="fas fa-shield-alt me-1"></i>Admin</span>
|
||||||
|
@else
|
||||||
|
<span class="badge-role badge-user"><i class="fas fa-user me-1"></i>User</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Info Detail --}}
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label"><i class="fas fa-hashtag me-2 text-muted"></i>ID</div>
|
||||||
|
<div class="info-value">#{{ $user->id }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label"><i class="fas fa-envelope me-2 text-muted"></i>Email</div>
|
||||||
|
<div class="info-value">{{ $user->email }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label"><i class="fas fa-check-circle me-2 text-muted"></i>Verifikasi</div>
|
||||||
|
<div class="info-value">
|
||||||
|
@if($user->email_verified_at)
|
||||||
|
<span class="badge-role badge-verified">
|
||||||
|
<i class="fas fa-check me-1"></i>Terverifikasi
|
||||||
|
</span>
|
||||||
|
<small class="text-muted ms-2">{{ $user->email_verified_at->format('d M Y') }}</small>
|
||||||
|
@else
|
||||||
|
<span class="badge-role badge-unverified">Belum Verifikasi</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label"><i class="fas fa-phone me-2 text-muted"></i>Telepon</div>
|
||||||
|
<div class="info-value">{{ $user->phone ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label"><i class="fas fa-map-marker-alt me-2 text-muted"></i>Alamat</div>
|
||||||
|
<div class="info-value">{{ $user->address ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label"><i class="fas fa-calendar me-2 text-muted"></i>Bergabung</div>
|
||||||
|
<div class="info-value">{{ $user->created_at->format('d M Y, H:i') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label"><i class="fas fa-sync me-2 text-muted"></i>Diperbarui</div>
|
||||||
|
<div class="info-value">{{ $user->updated_at->format('d M Y, H:i') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Action Buttons --}}
|
||||||
|
<div class="d-flex gap-2 mt-4">
|
||||||
|
<a href="{{ route('admin.users.edit', $user) }}" class="btn btn-warning">
|
||||||
|
<i class="fas fa-edit me-2"></i>Edit Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.users') }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||||
|
</a>
|
||||||
|
@if($user->id !== auth()->id())
|
||||||
|
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
||||||
|
class="ms-auto" onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
|
||||||
|
@csrf @method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,304 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Verifikasi Kode Admin - TA Bibit Cabai</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.btn-primary-custom {
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.btn-primary-custom:hover {
|
||||||
|
background: linear-gradient(135deg, #0d7377 0%, #2dd654 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(17, 153, 142, 0.3);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.btn-primary-custom:disabled {
|
||||||
|
background: #adb5bd;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #11998e;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(17, 153, 142, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
.step-circle {
|
||||||
|
width: 36px; height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-weight: 700; font-size: 13px;
|
||||||
|
}
|
||||||
|
.step-circle.active { background: #11998e; color: #fff; }
|
||||||
|
.step-circle.done { background: #11998e; color: #fff; }
|
||||||
|
.step-circle.idle { background: #dee2e6; color: #888; }
|
||||||
|
.step-label { font-size: 11px; white-space: nowrap; margin-top: 4px; }
|
||||||
|
.step-label.active { color: #11998e; font-weight: 700; }
|
||||||
|
.step-label.done { color: #11998e; }
|
||||||
|
.step-label.idle { color: #aaa; }
|
||||||
|
.step-item { display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.step-line { height: 2px; width: 36px; margin: 0 6px 18px; }
|
||||||
|
.step-line.done { background: #11998e; }
|
||||||
|
.step-line.idle { background: #dee2e6; }
|
||||||
|
|
||||||
|
/* OTP Boxes */
|
||||||
|
.otp-box {
|
||||||
|
width: 46px !important;
|
||||||
|
height: 54px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
border: 2px solid #dee2e6 !important;
|
||||||
|
font-size: 22px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
text-align: center;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.otp-box:focus {
|
||||||
|
border-color: #11998e !important;
|
||||||
|
box-shadow: 0 0 0 3px rgba(17,153,142,0.2) !important;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
.otp-box.filled {
|
||||||
|
border-color: #11998e !important;
|
||||||
|
background-color: #f0fff4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-badge {
|
||||||
|
background: #f0fff4;
|
||||||
|
border: 1px solid #11998e33;
|
||||||
|
border-left: 3px solid #11998e;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
width: 64px; height: 64px;
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-4 col-lg-4">
|
||||||
|
<div class="card login-card">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
|
||||||
|
<div class="icon-box">🛡️</div>
|
||||||
|
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<h4 class="fw-bold text-dark mb-1">Verifikasi Kode OTP</h4>
|
||||||
|
<p class="text-muted small">Masukkan kode 6 digit yang dikirim ke email</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Step Indicator --}}
|
||||||
|
<div class="step-wrapper">
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle done">✓</div>
|
||||||
|
<div class="step-label done">Email</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line done"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle active">2</div>
|
||||||
|
<div class="step-label active">Kode OTP</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line idle"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle idle">3</div>
|
||||||
|
<div class="step-label idle">Password Baru</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show py-2" role="alert">
|
||||||
|
<small>✅ {{ session('success') }}</small>
|
||||||
|
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show py-2" role="alert">
|
||||||
|
<small>⚠️ {{ session('error') }}</small>
|
||||||
|
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Email Target Badge --}}
|
||||||
|
<div class="email-badge mb-4 d-flex align-items-center gap-2">
|
||||||
|
<span style="font-size:20px;">📧</span>
|
||||||
|
<div>
|
||||||
|
<div class="fw-semibold text-dark small">Kode dikirim ke:</div>
|
||||||
|
<div class="text-muted" style="font-size:13px;">{{ session('admin_reset_email', 'email admin') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.password.verify-otp') }}" id="otpForm" novalidate>
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold text-center d-block">
|
||||||
|
Kode Verifikasi (6 digit)
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{-- OTP Boxes --}}
|
||||||
|
<div class="d-flex justify-content-center gap-2 mb-2" id="otpBoxes">
|
||||||
|
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="otp" id="otpHidden">
|
||||||
|
|
||||||
|
@error('otp')
|
||||||
|
<div class="text-danger text-center mt-1 small">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Countdown Timer --}}
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div id="timerWrapper">
|
||||||
|
<p class="text-muted small mb-1">Kode berlaku selama:</p>
|
||||||
|
<span id="countdown" class="badge fs-6 px-3 py-2" style="background:#11998e;">10:00</span>
|
||||||
|
</div>
|
||||||
|
<div id="expiredWrapper" style="display:none;">
|
||||||
|
<span class="badge bg-danger fs-6 px-3 py-2">
|
||||||
|
❌ Kode sudah kadaluarsa
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary-custom py-2 fw-bold" id="submitBtn" disabled>
|
||||||
|
✅ Verifikasi Kode
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr class="my-3">
|
||||||
|
|
||||||
|
{{-- Resend --}}
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-muted small mb-2">Tidak menerima kode?</p>
|
||||||
|
<form method="POST" action="{{ route('admin.password.resend-otp') }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-secondary btn-sm" id="resendBtn" disabled>
|
||||||
|
🔄 Kirim Ulang Kode
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div class="mt-2">
|
||||||
|
<a href="{{ route('admin.password.request') }}" class="text-decoration-none small text-muted">
|
||||||
|
← Ganti email
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const boxes = document.querySelectorAll('.otp-box');
|
||||||
|
const otpHidden = document.getElementById('otpHidden');
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
const resendBtn = document.getElementById('resendBtn');
|
||||||
|
const countdown = document.getElementById('countdown');
|
||||||
|
const timerWrap = document.getElementById('timerWrapper');
|
||||||
|
const expiredWrap= document.getElementById('expiredWrapper');
|
||||||
|
|
||||||
|
// ---- OTP input logic ----
|
||||||
|
boxes.forEach((box, i) => {
|
||||||
|
box.addEventListener('input', function () {
|
||||||
|
this.value = this.value.replace(/[^0-9]/g, '');
|
||||||
|
if (this.value) {
|
||||||
|
this.classList.add('filled');
|
||||||
|
if (i < boxes.length - 1) boxes[i + 1].focus();
|
||||||
|
} else {
|
||||||
|
this.classList.remove('filled');
|
||||||
|
}
|
||||||
|
updateHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
box.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Backspace' && !this.value && i > 0) {
|
||||||
|
boxes[i - 1].focus();
|
||||||
|
boxes[i - 1].value = '';
|
||||||
|
boxes[i - 1].classList.remove('filled');
|
||||||
|
updateHidden();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
box.addEventListener('paste', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const pasted = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g, '');
|
||||||
|
pasted.split('').slice(0, 6).forEach((ch, idx) => {
|
||||||
|
if (boxes[idx]) { boxes[idx].value = ch; boxes[idx].classList.add('filled'); }
|
||||||
|
});
|
||||||
|
updateHidden();
|
||||||
|
boxes[Math.min(pasted.length, 5)].focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateHidden() {
|
||||||
|
const otp = Array.from(boxes).map(b => b.value).join('');
|
||||||
|
otpHidden.value = otp;
|
||||||
|
submitBtn.disabled = otp.length !== 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Countdown 10 min ----
|
||||||
|
let secs = 10 * 60;
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
secs--;
|
||||||
|
const m = String(Math.floor(secs / 60)).padStart(2, '0');
|
||||||
|
const s = String(secs % 60).padStart(2, '0');
|
||||||
|
countdown.textContent = `${m}:${s}`;
|
||||||
|
|
||||||
|
if (secs <= 60) { countdown.style.background = '#ffc107'; countdown.style.color = '#000'; }
|
||||||
|
if (secs <= 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
timerWrap.style.display = 'none';
|
||||||
|
expiredWrap.style.display = 'block';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
resendBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Enable resend after 60s
|
||||||
|
setTimeout(() => { resendBtn.disabled = false; }, 60000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Lupa Password - Shop Bibit Cabai Bondowoso')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 col-lg-5">
|
||||||
|
<div class="card border-0 shadow-lg">
|
||||||
|
<div class="card-header bg-success text-white text-center py-4">
|
||||||
|
<h3 class="mb-0">
|
||||||
|
<i class="fas fa-key me-2"></i>Lupa Password
|
||||||
|
</h3>
|
||||||
|
<p class="mb-0 mt-2 opacity-75 small">Masukkan email terdaftar Anda</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-4">
|
||||||
|
|
||||||
|
{{-- Step Indicator --}}
|
||||||
|
<div class="d-flex align-items-center justify-content-center mb-4">
|
||||||
|
<div class="step-item active">
|
||||||
|
<div class="step-circle bg-success text-white">1</div>
|
||||||
|
<div class="step-label text-success fw-bold">Email</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line bg-secondary mx-2"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle bg-secondary text-white">2</div>
|
||||||
|
<div class="step-label text-secondary">Kode OTP</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line bg-secondary mx-2"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle bg-secondary text-white">3</div>
|
||||||
|
<div class="step-label text-secondary">Password Baru</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<p class="text-muted text-center mb-4">
|
||||||
|
<i class="fas fa-info-circle text-success me-1"></i>
|
||||||
|
Kami akan mengirim kode verifikasi ke email Anda untuk mereset password.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('password.email') }}" id="forgotForm">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="email" class="form-label fw-semibold">
|
||||||
|
Email <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light">
|
||||||
|
<i class="fas fa-envelope text-success"></i>
|
||||||
|
</span>
|
||||||
|
<input type="email"
|
||||||
|
class="form-control @error('email') is-invalid @enderror"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
value="{{ old('email') }}"
|
||||||
|
required
|
||||||
|
autofocus
|
||||||
|
placeholder="contoh@gmail.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-text text-muted">
|
||||||
|
<small><i class="fas fa-info-circle"></i> Gunakan email @gmail.com yang terdaftar</small>
|
||||||
|
</div>
|
||||||
|
@error('email')
|
||||||
|
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||||
|
@enderror
|
||||||
|
<div id="emailError" class="text-danger mt-1" style="display: none;">
|
||||||
|
<small>Email harus menggunakan domain @gmail.com</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-success btn-lg" id="submitBtn" disabled>
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>Kirim Kode Verifikasi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="{{ route('login') }}" class="text-success text-decoration-none">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>Kembali ke Halaman Login
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.step-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.step-circle {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.step-label {
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.step-line {
|
||||||
|
height: 2px;
|
||||||
|
width: 40px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const emailInput = document.getElementById('email');
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
const emailError = document.getElementById('emailError');
|
||||||
|
|
||||||
|
emailInput.addEventListener('input', function () {
|
||||||
|
const val = this.value;
|
||||||
|
const gmailPattern = /@gmail\.com$/;
|
||||||
|
const hasSpaces = /\s/.test(val);
|
||||||
|
|
||||||
|
if (val.length > 0 && (!gmailPattern.test(val) || hasSpaces)) {
|
||||||
|
emailError.style.display = 'block';
|
||||||
|
this.classList.add('is-invalid');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
} else {
|
||||||
|
emailError.style.display = 'none';
|
||||||
|
this.classList.remove('is-invalid');
|
||||||
|
submitBtn.disabled = val.length === 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emailInput.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === ' ') e.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|
@ -74,7 +74,11 @@ class="form-control @error('password') is-invalid @enderror"
|
||||||
<small>Password minimal 8 karakter dan tidak boleh mengandung spasi</small>
|
<small>Password minimal 8 karakter dan tidak boleh mengandung spasi</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-end mb-3">
|
||||||
|
<a href="{{ route('password.request') }}" class="text-success text-decoration-none small">
|
||||||
|
<i class="fas fa-question-circle me-1"></i>Lupa Password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="mb-3 form-check">
|
<div class="mb-3 form-check">
|
||||||
<input type="checkbox" class="form-check-input @error('remember') is-invalid @enderror" id="remember" name="remember" required>
|
<input type="checkbox" class="form-check-input @error('remember') is-invalid @enderror" id="remember" name="remember" required>
|
||||||
<label class="form-check-label" for="remember">
|
<label class="form-check-label" for="remember">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Reset Password - Shop Bibit Cabai Bondowoso')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 col-lg-5">
|
||||||
|
<div class="card border-0 shadow-lg">
|
||||||
|
<div class="card-header bg-success text-white text-center py-4">
|
||||||
|
<h3 class="mb-0">
|
||||||
|
<i class="fas fa-lock-open me-2"></i>Buat Password Baru
|
||||||
|
</h3>
|
||||||
|
<p class="mb-0 mt-2 opacity-75 small">Buat password yang kuat untuk akun Anda</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-4">
|
||||||
|
|
||||||
|
{{-- Step Indicator --}}
|
||||||
|
<div class="d-flex align-items-center justify-content-center mb-4">
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle bg-success text-white">
|
||||||
|
<i class="fas fa-check" style="font-size:12px;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="step-label text-success fw-bold">Email</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line bg-success mx-2"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle bg-success text-white">
|
||||||
|
<i class="fas fa-check" style="font-size:12px;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="step-label text-success fw-bold">Kode OTP</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line bg-success mx-2"></div>
|
||||||
|
<div class="step-item active">
|
||||||
|
<div class="step-circle bg-success text-white">3</div>
|
||||||
|
<div class="step-label text-success fw-bold">Password Baru</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<p class="text-muted text-center mb-4">
|
||||||
|
<i class="fas fa-check-circle text-success me-1"></i>
|
||||||
|
Email terverifikasi. Silakan buat password baru Anda.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('password.update') }}" id="resetForm">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
{{-- Hidden token from session --}}
|
||||||
|
<input type="hidden" name="token" value="{{ session('reset_token') }}">
|
||||||
|
<input type="hidden" name="email" value="{{ session('reset_email') }}">
|
||||||
|
|
||||||
|
{{-- New Password --}}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label fw-semibold">
|
||||||
|
Password Baru <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light">
|
||||||
|
<i class="fas fa-lock text-success"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control @error('password') is-invalid @enderror"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
placeholder="Minimal 8 karakter">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="togglePassword">
|
||||||
|
<i class="fas fa-eye" id="eyeIcon1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text text-muted">
|
||||||
|
<small><i class="fas fa-info-circle"></i> Minimal 8 karakter, tanpa spasi</small>
|
||||||
|
</div>
|
||||||
|
@error('password')
|
||||||
|
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||||
|
@enderror
|
||||||
|
<div id="passwordError" class="text-danger mt-1" style="display:none;">
|
||||||
|
<small>Password minimal 8 karakter dan tidak boleh mengandung spasi</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Password Strength Bar --}}
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<small class="text-muted">Kekuatan password:</small>
|
||||||
|
<small id="strengthLabel" class="fw-semibold text-secondary">—</small>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 6px; border-radius: 3px;">
|
||||||
|
<div id="strengthBar" class="progress-bar bg-secondary"
|
||||||
|
role="progressbar" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Confirm Password --}}
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="password_confirmation" class="form-label fw-semibold">
|
||||||
|
Konfirmasi Password <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light">
|
||||||
|
<i class="fas fa-lock text-success"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control @error('password_confirmation') is-invalid @enderror"
|
||||||
|
id="password_confirmation"
|
||||||
|
name="password_confirmation"
|
||||||
|
required
|
||||||
|
placeholder="Ulangi password baru">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="toggleConfirm">
|
||||||
|
<i class="fas fa-eye" id="eyeIcon2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@error('password_confirmation')
|
||||||
|
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||||
|
@enderror
|
||||||
|
<div id="confirmError" class="text-danger mt-1" style="display:none;">
|
||||||
|
<small>Password tidak cocok</small>
|
||||||
|
</div>
|
||||||
|
<div id="confirmSuccess" class="text-success mt-1" style="display:none;">
|
||||||
|
<small><i class="fas fa-check-circle me-1"></i>Password cocok</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-success btn-lg" id="submitBtn" disabled>
|
||||||
|
<i class="fas fa-save me-2"></i>Simpan Password Baru
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.step-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.step-circle {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.step-label {
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.step-line {
|
||||||
|
height: 2px;
|
||||||
|
width: 40px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
const confirmInput = document.getElementById('password_confirmation');
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
const strengthBar = document.getElementById('strengthBar');
|
||||||
|
const strengthLabel = document.getElementById('strengthLabel');
|
||||||
|
|
||||||
|
// Toggle password visibility
|
||||||
|
document.getElementById('togglePassword').addEventListener('click', function () {
|
||||||
|
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||||
|
passwordInput.setAttribute('type', type);
|
||||||
|
document.getElementById('eyeIcon1').classList.toggle('fa-eye');
|
||||||
|
document.getElementById('eyeIcon1').classList.toggle('fa-eye-slash');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('toggleConfirm').addEventListener('click', function () {
|
||||||
|
const type = confirmInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||||
|
confirmInput.setAttribute('type', type);
|
||||||
|
document.getElementById('eyeIcon2').classList.toggle('fa-eye');
|
||||||
|
document.getElementById('eyeIcon2').classList.toggle('fa-eye-slash');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Password strength checker
|
||||||
|
function checkStrength(pwd) {
|
||||||
|
let score = 0;
|
||||||
|
if (pwd.length >= 8) score++;
|
||||||
|
if (pwd.length >= 12) score++;
|
||||||
|
if (/[A-Z]/.test(pwd)) score++;
|
||||||
|
if (/[0-9]/.test(pwd)) score++;
|
||||||
|
if (/[^A-Za-z0-9]/.test(pwd)) score++;
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordInput.addEventListener('input', function () {
|
||||||
|
const val = this.value;
|
||||||
|
const hasSpaces = /\s/.test(val);
|
||||||
|
const passwordError = document.getElementById('passwordError');
|
||||||
|
|
||||||
|
if (val.length > 0 && (val.length < 8 || hasSpaces)) {
|
||||||
|
passwordError.style.display = 'block';
|
||||||
|
this.classList.add('is-invalid');
|
||||||
|
} else {
|
||||||
|
passwordError.style.display = 'none';
|
||||||
|
this.classList.remove('is-invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strength bar
|
||||||
|
if (val.length === 0) {
|
||||||
|
strengthBar.style.width = '0%';
|
||||||
|
strengthBar.className = 'progress-bar bg-secondary';
|
||||||
|
strengthLabel.textContent = '—';
|
||||||
|
strengthLabel.className = 'fw-semibold text-secondary';
|
||||||
|
} else {
|
||||||
|
const score = checkStrength(val);
|
||||||
|
const levels = [
|
||||||
|
{ pct: '20%', cls: 'bg-danger', label: 'Sangat Lemah', labelCls: 'text-danger' },
|
||||||
|
{ pct: '40%', cls: 'bg-warning', label: 'Lemah', labelCls: 'text-warning' },
|
||||||
|
{ pct: '60%', cls: 'bg-info', label: 'Sedang', labelCls: 'text-info' },
|
||||||
|
{ pct: '80%', cls: 'bg-primary', label: 'Kuat', labelCls: 'text-primary' },
|
||||||
|
{ pct: '100%', cls: 'bg-success', label: 'Sangat Kuat', labelCls: 'text-success' },
|
||||||
|
];
|
||||||
|
const lvl = levels[Math.min(score - 1, 4)] || levels[0];
|
||||||
|
strengthBar.style.width = lvl.pct;
|
||||||
|
strengthBar.className = `progress-bar ${lvl.cls}`;
|
||||||
|
strengthLabel.textContent = lvl.label;
|
||||||
|
strengthLabel.className = `fw-semibold ${lvl.labelCls}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateConfirm();
|
||||||
|
checkFormValidity();
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmInput.addEventListener('input', function () {
|
||||||
|
validateConfirm();
|
||||||
|
checkFormValidity();
|
||||||
|
});
|
||||||
|
|
||||||
|
function validateConfirm() {
|
||||||
|
const confirmError = document.getElementById('confirmError');
|
||||||
|
const confirmSuccess = document.getElementById('confirmSuccess');
|
||||||
|
if (confirmInput.value.length === 0) {
|
||||||
|
confirmError.style.display = 'none';
|
||||||
|
confirmSuccess.style.display = 'none';
|
||||||
|
confirmInput.classList.remove('is-invalid', 'is-valid');
|
||||||
|
} else if (passwordInput.value !== confirmInput.value) {
|
||||||
|
confirmError.style.display = 'block';
|
||||||
|
confirmSuccess.style.display = 'none';
|
||||||
|
confirmInput.classList.add('is-invalid');
|
||||||
|
confirmInput.classList.remove('is-valid');
|
||||||
|
} else {
|
||||||
|
confirmError.style.display = 'none';
|
||||||
|
confirmSuccess.style.display = 'block';
|
||||||
|
confirmInput.classList.remove('is-invalid');
|
||||||
|
confirmInput.classList.add('is-valid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkFormValidity() {
|
||||||
|
const pwValid = passwordInput.value.length >= 8 && !/\s/.test(passwordInput.value);
|
||||||
|
const confirmValid = confirmInput.value === passwordInput.value && confirmInput.value.length > 0;
|
||||||
|
submitBtn.disabled = !(pwValid && confirmValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent spaces
|
||||||
|
[passwordInput, confirmInput].forEach(input => {
|
||||||
|
input.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === ' ') e.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Verifikasi Kode - Shop Bibit Cabai Bondowoso')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 col-lg-5">
|
||||||
|
<div class="card border-0 shadow-lg">
|
||||||
|
<div class="card-header bg-success text-white text-center py-4">
|
||||||
|
<h3 class="mb-0">
|
||||||
|
<i class="fas fa-shield-alt me-2"></i>Verifikasi Kode
|
||||||
|
</h3>
|
||||||
|
<p class="mb-0 mt-2 opacity-75 small">Masukkan kode yang dikirim ke email Anda</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-4">
|
||||||
|
|
||||||
|
{{-- Step Indicator --}}
|
||||||
|
<div class="d-flex align-items-center justify-content-center mb-4">
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle bg-success text-white">
|
||||||
|
<i class="fas fa-check" style="font-size:12px;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="step-label text-success fw-bold">Email</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line bg-success mx-2"></div>
|
||||||
|
<div class="step-item active">
|
||||||
|
<div class="step-circle bg-success text-white">2</div>
|
||||||
|
<div class="step-label text-success fw-bold">Kode OTP</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-line bg-secondary mx-2"></div>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="step-circle bg-secondary text-white">3</div>
|
||||||
|
<div class="step-label text-secondary">Password Baru</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Info email target --}}
|
||||||
|
<div class="alert alert-light border-start border-success border-3 mb-4">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="fas fa-envelope text-success me-3 fs-5"></i>
|
||||||
|
<div>
|
||||||
|
<div class="fw-semibold text-dark">Kode dikirim ke:</div>
|
||||||
|
<div class="text-muted small">{{ session('reset_email', 'email Anda') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('password.verify-otp') }}" id="otpForm">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold text-center d-block">
|
||||||
|
Kode Verifikasi (6 digit) <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{-- OTP Input Boxes --}}
|
||||||
|
<div class="d-flex justify-content-center gap-2 mb-2" id="otpBoxes">
|
||||||
|
<input type="text" class="otp-box form-control text-center fw-bold fs-4"
|
||||||
|
maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control text-center fw-bold fs-4"
|
||||||
|
maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control text-center fw-bold fs-4"
|
||||||
|
maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control text-center fw-bold fs-4"
|
||||||
|
maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control text-center fw-bold fs-4"
|
||||||
|
maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
<input type="text" class="otp-box form-control text-center fw-bold fs-4"
|
||||||
|
maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Hidden input to store full OTP --}}
|
||||||
|
<input type="hidden" name="otp" id="otpHidden">
|
||||||
|
|
||||||
|
@error('otp')
|
||||||
|
<div class="text-danger text-center mt-1"><small>{{ $message }}</small></div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Countdown Timer --}}
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div id="timerWrapper">
|
||||||
|
<p class="text-muted small mb-1">Kode berlaku selama:</p>
|
||||||
|
<span id="countdown" class="badge bg-success fs-6 px-3 py-2">10:00</span>
|
||||||
|
</div>
|
||||||
|
<div id="expiredWrapper" style="display:none;">
|
||||||
|
<span class="badge bg-danger fs-6 px-3 py-2">
|
||||||
|
<i class="fas fa-times-circle me-1"></i>Kode sudah kadaluarsa
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-success btn-lg" id="submitBtn" disabled>
|
||||||
|
<i class="fas fa-check-circle me-2"></i>Verifikasi Kode
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
{{-- Resend OTP --}}
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-muted small mb-2">Tidak menerima kode?</p>
|
||||||
|
<form method="POST" action="{{ route('password.resend-otp') }}" id="resendForm">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn btn-outline-success btn-sm" id="resendBtn" disabled>
|
||||||
|
<i class="fas fa-redo me-1"></i>Kirim Ulang Kode
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div class="mt-2">
|
||||||
|
<a href="{{ route('password.request') }}" class="text-muted text-decoration-none small">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>Ganti email
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.step-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.step-circle {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.step-label {
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.step-line {
|
||||||
|
height: 2px;
|
||||||
|
width: 40px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.otp-box {
|
||||||
|
width: 48px !important;
|
||||||
|
height: 56px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
border: 2px solid #dee2e6 !important;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.otp-box:focus {
|
||||||
|
border-color: #198754 !important;
|
||||||
|
box-shadow: 0 0 0 3px rgba(25,135,84,0.2) !important;
|
||||||
|
}
|
||||||
|
.otp-box.filled {
|
||||||
|
border-color: #198754 !important;
|
||||||
|
background-color: #f0fff4 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const boxes = document.querySelectorAll('.otp-box');
|
||||||
|
const otpHidden = document.getElementById('otpHidden');
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
const resendBtn = document.getElementById('resendBtn');
|
||||||
|
const countdownEl = document.getElementById('countdown');
|
||||||
|
const timerWrapper = document.getElementById('timerWrapper');
|
||||||
|
const expiredWrapper = document.getElementById('expiredWrapper');
|
||||||
|
|
||||||
|
// ---- OTP box logic ----
|
||||||
|
boxes.forEach((box, index) => {
|
||||||
|
box.addEventListener('input', function () {
|
||||||
|
this.value = this.value.replace(/[^0-9]/g, '');
|
||||||
|
if (this.value.length === 1) {
|
||||||
|
this.classList.add('filled');
|
||||||
|
if (index < boxes.length - 1) boxes[index + 1].focus();
|
||||||
|
} else {
|
||||||
|
this.classList.remove('filled');
|
||||||
|
}
|
||||||
|
updateHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
box.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Backspace' && this.value === '' && index > 0) {
|
||||||
|
boxes[index - 1].focus();
|
||||||
|
boxes[index - 1].value = '';
|
||||||
|
boxes[index - 1].classList.remove('filled');
|
||||||
|
updateHidden();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle paste
|
||||||
|
box.addEventListener('paste', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const pasted = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g, '');
|
||||||
|
pasted.split('').slice(0, 6).forEach((char, i) => {
|
||||||
|
if (boxes[i]) {
|
||||||
|
boxes[i].value = char;
|
||||||
|
boxes[i].classList.add('filled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateHidden();
|
||||||
|
const lastFilled = Math.min(pasted.length, 5);
|
||||||
|
boxes[lastFilled].focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateHidden() {
|
||||||
|
const otp = Array.from(boxes).map(b => b.value).join('');
|
||||||
|
otpHidden.value = otp;
|
||||||
|
submitBtn.disabled = otp.length !== 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Countdown Timer: 10 minutes ----
|
||||||
|
let seconds = 10 * 60;
|
||||||
|
|
||||||
|
function formatTime(s) {
|
||||||
|
const m = Math.floor(s / 60).toString().padStart(2, '0');
|
||||||
|
const sec = (s % 60).toString().padStart(2, '0');
|
||||||
|
return `${m}:${sec}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timer = setInterval(function () {
|
||||||
|
seconds--;
|
||||||
|
countdownEl.textContent = formatTime(seconds);
|
||||||
|
|
||||||
|
if (seconds <= 60) {
|
||||||
|
countdownEl.classList.replace('bg-success', 'bg-warning');
|
||||||
|
countdownEl.classList.add('text-dark');
|
||||||
|
}
|
||||||
|
if (seconds <= 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
timerWrapper.style.display = 'none';
|
||||||
|
expiredWrapper.style.display = 'block';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
// Enable resend after expiry
|
||||||
|
resendBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Enable resend after 60s
|
||||||
|
setTimeout(() => { resendBtn.disabled = false; }, 60000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|
@ -52,10 +52,14 @@
|
||||||
|
|
||||||
<h5 class="mb-3">Data Pembeli</h5>
|
<h5 class="mb-3">Data Pembeli</h5>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nama Lengkap *</label>
|
<label class="form-label">Nama Lengkap *</label>
|
||||||
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror"
|
<input type="text" name="name"
|
||||||
value="{{ old('name') }}" required>
|
class="form-control @error('name') is-invalid @enderror"
|
||||||
|
value="{{ old('name') }}"
|
||||||
|
placeholder="Minimal 8 karakter"
|
||||||
|
minlength="8" required>
|
||||||
|
<small class="text-muted">Minimal 8 karakter</small>
|
||||||
@error('name')
|
@error('name')
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
@enderror
|
@enderror
|
||||||
|
|
@ -63,21 +67,28 @@
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nomor Telepon *</label>
|
<label class="form-label">Nomor Telepon *</label>
|
||||||
<input type="text" name="phone" class="form-control @error('phone') is-invalid @enderror"
|
<input type="text" name="phone"
|
||||||
value="{{ old('phone') }}" placeholder="08123456789" required>
|
class="form-control @error('phone') is-invalid @enderror"
|
||||||
|
value="{{ old('phone') }}"
|
||||||
|
placeholder="+6281234567890"
|
||||||
|
minlength="11" required>
|
||||||
|
<small class="text-muted">Harus diawali +62, contoh: +6281234567890</small>
|
||||||
@error('phone')
|
@error('phone')
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
<div class="mb-3">
|
<label class="form-label">Email *</label>
|
||||||
<label class="form-label">Email *</label>
|
<input type="email" name="email"
|
||||||
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror"
|
class="form-control @error('email') is-invalid @enderror"
|
||||||
value="{{ old('email') }}" required>
|
value="{{ old('email') }}"
|
||||||
@error('email')
|
placeholder="contoh@email.com"
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
minlength="8" required>
|
||||||
@enderror
|
<small class="text-muted">Minimal 8 karakter</small>
|
||||||
</div>
|
@error('email')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<h5 class="mb-3 mt-4">Alamat Pengiriman</h5>
|
<h5 class="mb-3 mt-4">Alamat Pengiriman</h5>
|
||||||
|
|
||||||
|
|
@ -131,21 +142,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Alamat Lengkap (Nama Jalan, No Rumah, RT/RW, Desa) *</label>
|
<label class="form-label">Alamat Lengkap *</label>
|
||||||
<textarea name="address" class="form-control @error('address') is-invalid @enderror"
|
<textarea name="address"
|
||||||
rows="3" placeholder="Contoh: Jl. Merdeka No. 123, RT 02/RW 05, Desa Kademangan" required>{{ old('address') }}</textarea>
|
class="form-control @error('address') is-invalid @enderror"
|
||||||
@error('address')
|
rows="4"
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
placeholder="Contoh: Jl. Merdeka No. 12, RT 02/RW 05, Desa Kademangan, Arah dari pasar ke utara 200m"
|
||||||
@enderror
|
minlength="20" required>{{ old('address') }}</textarea>
|
||||||
</div>
|
<small class="text-muted">
|
||||||
|
Wajib lengkap: Nama Jalan · No. Rumah · RT/RW · Desa/Kelurahan · Ancer-ancer
|
||||||
|
</small>
|
||||||
|
@error('address')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="province" value="Jawa Timur">
|
<input type="hidden" name="province" value="Jawa Timur">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Kode Pos *</label>
|
<label class="form-label">Kode Pos *</label>
|
||||||
<input type="text" name="postal_code" class="form-control @error('postal_code') is-invalid @enderror"
|
<input type="text" name="postal_code"
|
||||||
value="{{ old('postal_code', '68200') }}" placeholder="68200" required>
|
class="form-control @error('postal_code') is-invalid @enderror"
|
||||||
|
value="{{ old('postal_code', '68200') }}"
|
||||||
|
placeholder="68200"
|
||||||
|
maxlength="5" required>
|
||||||
@error('postal_code')
|
@error('postal_code')
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
@enderror
|
@enderror
|
||||||
|
|
@ -387,21 +407,20 @@ class="border p-2 bg-white"> -->
|
||||||
ongkirLoading.style.display = 'block';
|
ongkirLoading.style.display = 'block';
|
||||||
ongkirInfo.style.display = 'none';
|
ongkirInfo.style.display = 'none';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const baseOngkir = ongkirData[kecamatan] || 15000;
|
const baseOngkir = ongkirData[kecamatan] || 15000;
|
||||||
let totalOngkir = baseOngkir;
|
let totalOngkir = baseOngkir;
|
||||||
let detail = `Base ongkir: Rp ${formatRupiah(baseOngkir)}`;
|
let detail = `Ongkir ke ${kecamatan}: Rp ${formatRupiah(baseOngkir)}`;
|
||||||
|
|
||||||
if (quantity > 5) {
|
// Gratis ongkir >= 800 bibit
|
||||||
const extraBibit = quantity - 5;
|
if (quantity >= 800) {
|
||||||
const extraCost = extraBibit * 5000;
|
|
||||||
totalOngkir += extraCost;
|
|
||||||
detail += ` + Tambahan ${extraBibit} bibit: Rp ${formatRupiah(extraCost)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (quantity >= 100) {
|
|
||||||
totalOngkir = 0;
|
totalOngkir = 0;
|
||||||
detail = '🎉 GRATIS ONGKIR untuk pembelian 100+ bibit!';
|
detail = '🎉 GRATIS ONGKIR untuk pembelian 800+ bibit!';
|
||||||
|
}
|
||||||
|
// >= 1000 bibit: gratis ongkir + diskon 15%
|
||||||
|
if (quantity >= 1000) {
|
||||||
|
totalOngkir = 0;
|
||||||
|
detail = '🎉 GRATIS ONGKIR + Diskon 15% untuk pembelian 1000+ bibit!';
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('ongkirKecamatan').textContent = kecamatan;
|
document.getElementById('ongkirKecamatan').textContent = kecamatan;
|
||||||
|
|
@ -465,10 +484,41 @@ class="border p-2 bg-white"> -->
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateTotal(ongkir) {
|
function updateTotal(ongkir) {
|
||||||
const total = subtotal + ongkir;
|
// Hitung diskon
|
||||||
|
let diskon = 0;
|
||||||
|
let diskonLabel = '';
|
||||||
|
|
||||||
|
if (quantity >= 1000) {
|
||||||
|
diskon = subtotal * 0.15;
|
||||||
|
diskonLabel = '🎉 Diskon 15% untuk pembelian 1000+ bibit!';
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalTotal = (subtotal - diskon) + ongkir;
|
||||||
|
|
||||||
document.getElementById('ongkirDisplay').textContent = 'Rp ' + formatRupiah(ongkir);
|
document.getElementById('ongkirDisplay').textContent = 'Rp ' + formatRupiah(ongkir);
|
||||||
document.getElementById('totalDisplay').textContent = 'Rp ' + formatRupiah(total);
|
document.getElementById('totalDisplay').textContent = 'Rp ' + formatRupiah(finalTotal);
|
||||||
|
|
||||||
|
// Tampilkan/sembunyikan baris diskon
|
||||||
|
let diskonEl = document.getElementById('diskonRow');
|
||||||
|
if (!diskonEl && diskon > 0) {
|
||||||
|
// Buat baris diskon jika belum ada
|
||||||
|
const ongkirRow = document.querySelector('.total-section hr');
|
||||||
|
const rowHtml = `
|
||||||
|
<div class="d-flex justify-content-between mb-2 text-danger" id="diskonRow">
|
||||||
|
<span>${diskonLabel}</span>
|
||||||
|
<span><strong>-Rp ${formatRupiah(diskon)}</strong></span>
|
||||||
|
</div>`;
|
||||||
|
ongkirRow.insertAdjacentHTML('beforebegin', rowHtml);
|
||||||
|
} else if (diskonEl) {
|
||||||
|
if (diskon > 0) {
|
||||||
|
diskonEl.style.display = 'flex';
|
||||||
|
diskonEl.querySelector('span').textContent = diskonLabel;
|
||||||
|
diskonEl.querySelector('strong').textContent = '-Rp ' + formatRupiah(diskon);
|
||||||
|
} else {
|
||||||
|
diskonEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRupiah(angka) {
|
function formatRupiah(angka) {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,75 @@
|
||||||
.copy-btn:hover {
|
.copy-btn:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 10mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area,
|
||||||
|
#print-area * {
|
||||||
|
visibility: visible;
|
||||||
|
height: auto;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area .card {
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 1px solid #dee2e6 !important;
|
||||||
|
margin-top: 8px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area .card-body {
|
||||||
|
padding: 10px 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area h5 {
|
||||||
|
font-size: 0.9rem !important;
|
||||||
|
margin-bottom: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area .order-detail-row {
|
||||||
|
padding: 5px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area p,
|
||||||
|
#print-area span,
|
||||||
|
#print-area td,
|
||||||
|
#print-area small {
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area img {
|
||||||
|
width: 50px !important;
|
||||||
|
height: 50px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area hr {
|
||||||
|
margin: 5px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-area .mb-2,
|
||||||
|
#print-area .mb-3 {
|
||||||
|
margin-bottom: 4px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
|
@ -65,15 +134,15 @@
|
||||||
<div class="container my-5">
|
<div class="container my-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
|
|
||||||
|
{{-- Card Pesanan Berhasil (tidak ikut print) --}}
|
||||||
<div class="card success-card">
|
<div class="card success-card">
|
||||||
<div class="card-body p-5 text-center">
|
<div class="card-body p-5 text-center">
|
||||||
<div class="success-icon">
|
<div class="success-icon">
|
||||||
<i class="fas fa-check fa-3x text-white"></i>
|
<i class="fas fa-check fa-3x text-white"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="fw-bold text-success mb-3">Pesanan Berhasil!</h2>
|
<h2 class="fw-bold text-success mb-3">Pesanan Berhasil!</h2>
|
||||||
<p class="text-muted mb-4">Terima kasih atas pesanan Anda. Pesanan Anda sedang diproses.</p>
|
<p class="text-muted mb-4">Terima kasih atas pesanan Anda. Pesanan Anda sedang diproses.</p>
|
||||||
|
|
||||||
<div class="alert alert-success" role="alert">
|
<div class="alert alert-success" role="alert">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
Nomor Invoice: <strong>{{ $transaksi->invoice_number }}</strong>
|
Nomor Invoice: <strong>{{ $transaksi->invoice_number }}</strong>
|
||||||
|
|
@ -81,243 +150,222 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Detail Pesanan -->
|
{{-- ===== MULAI AREA PRINT ===== --}}
|
||||||
<div class="card success-card mt-4">
|
<div id="print-area">
|
||||||
<div class="card-body p-4">
|
|
||||||
<h5 class="fw-bold mb-4">
|
|
||||||
<i class="fas fa-clipboard-list me-2 text-success"></i>Detail Pesanan
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="order-detail-row">
|
{{-- Card Detail Pesanan --}}
|
||||||
<div class="row">
|
<div class="card success-card mt-4">
|
||||||
<div class="col-6"><strong>Invoice:</strong></div>
|
<div class="card-body p-4">
|
||||||
<div class="col-6 text-end">{{ $transaksi->invoice_number }}</div>
|
<h5 class="fw-bold mb-4">
|
||||||
</div>
|
<i class="fas fa-clipboard-list me-2 text-success"></i>Detail Pesanan
|
||||||
</div>
|
</h5>
|
||||||
|
<div class="order-detail-row">
|
||||||
<div class="order-detail-row">
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col-6"><strong>Invoice:</strong></div>
|
||||||
<div class="col-6"><strong>Tanggal:</strong></div>
|
<div class="col-6 text-end">{{ $transaksi->invoice_number }}</div>
|
||||||
<div class="col-6 text-end">{{ $transaksi->created_at->format('d M Y H:i') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="order-detail-row">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6"><strong>Status Pesanan:</strong></div>
|
|
||||||
<div class="col-6 text-end">
|
|
||||||
<span class="badge bg-warning text-dark">Menunggu Pembayaran</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="order-detail-row">
|
||||||
<div class="order-detail-row">
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col-6"><strong>Tanggal:</strong></div>
|
||||||
<div class="col-6"><strong>Metode Pembayaran:</strong></div>
|
<div class="col-6 text-end">{{ $transaksi->created_at->format('d M Y H:i') }}</div>
|
||||||
<div class="col-6 text-end">
|
</div>
|
||||||
@if($transaksi->payment_method == 'qris')
|
|
||||||
<i class="fas fa-qrcode me-1"></i>QRIS
|
|
||||||
@elseif($transaksi->payment_method == 'bri')
|
|
||||||
<i class="fas fa-university me-1"></i>Transfer Bank BRI
|
|
||||||
@elseif($transaksi->payment_method == 'dana')
|
|
||||||
<i class="fas fa-wallet me-1"></i>DANA
|
|
||||||
@elseif($transaksi->payment_method == 'seabank')
|
|
||||||
<i class="fas fa-university me-1"></i>SeaBank
|
|
||||||
@elseif($transaksi->payment_method == 'shopee')
|
|
||||||
<i class="fas fa-shopping-bag me-1"></i>ShopeePay
|
|
||||||
@else
|
|
||||||
<i class="fas fa-money-bill-wave me-1"></i>COD
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="order-detail-row">
|
||||||
</div>
|
<div class="row">
|
||||||
<!-- Data Pembeli -->
|
<div class="col-6"><strong>Status Pesanan:</strong></div>
|
||||||
<div class="card success-card mt-4">
|
<div class="col-6 text-end">
|
||||||
<div class="card-body p-4">
|
<span class="badge bg-warning text-dark">Menunggu Pembayaran</span>
|
||||||
<h5 class="fw-bold mb-4">
|
</div>
|
||||||
<i class="fas fa-user me-2 text-success"></i>Data Pembeli
|
</div>
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="order-detail-row">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6"><strong>Nama:</strong></div>
|
|
||||||
<div class="col-6 text-end">{{ $transaksi->customer_name }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="order-detail-row">
|
||||||
|
<div class="row">
|
||||||
<div class="order-detail-row">
|
<div class="col-6"><strong>Metode Pembayaran:</strong></div>
|
||||||
<div class="row">
|
<div class="col-6 text-end">
|
||||||
<div class="col-6"><strong>Telepon:</strong></div>
|
@if($transaksi->payment_method == 'qris')
|
||||||
<div class="col-6 text-end">{{ $transaksi->customer_phone }}</div>
|
<i class="fas fa-qrcode me-1"></i>QRIS
|
||||||
</div>
|
@elseif($transaksi->payment_method == 'bri')
|
||||||
</div>
|
<i class="fas fa-university me-1"></i>Transfer Bank BRI
|
||||||
|
@elseif($transaksi->payment_method == 'dana')
|
||||||
<div class="order-detail-row">
|
<i class="fas fa-wallet me-1"></i>DANA
|
||||||
<div class="row">
|
@elseif($transaksi->payment_method == 'seabank')
|
||||||
<div class="col-6"><strong>Email:</strong></div>
|
<i class="fas fa-university me-1"></i>SeaBank
|
||||||
<div class="col-6 text-end">{{ $transaksi->customer_email }}</div>
|
@elseif($transaksi->payment_method == 'shopee')
|
||||||
</div>
|
<i class="fas fa-shopping-bag me-1"></i>ShopeePay
|
||||||
</div>
|
@else
|
||||||
|
<i class="fas fa-money-bill-wave me-1"></i>COD
|
||||||
<div class="order-detail-row">
|
@endif
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col-12"><strong>Alamat Pengiriman:</strong></div>
|
|
||||||
<div class="col-12 mt-2 text-muted">
|
|
||||||
{{ $transaksi->shipping_address }}<br>
|
|
||||||
{{ $transaksi->city }}, {{ $transaksi->province }} {{ $transaksi->postal_code }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Produk -->
|
{{-- Card Data Pembeli --}}
|
||||||
|
<div class="card success-card mt-4">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h5 class="fw-bold mb-4">
|
||||||
|
<i class="fas fa-user me-2 text-success"></i>Data Pembeli
|
||||||
|
</h5>
|
||||||
|
<div class="order-detail-row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6"><strong>Nama:</strong></div>
|
||||||
|
<div class="col-6 text-end">{{ $transaksi->customer_name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order-detail-row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6"><strong>Telepon:</strong></div>
|
||||||
|
<div class="col-6 text-end">{{ $transaksi->customer_phone }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order-detail-row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6"><strong>Email:</strong></div>
|
||||||
|
<div class="col-6 text-end">{{ $transaksi->customer_email }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order-detail-row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12"><strong>Alamat Pengiriman:</strong></div>
|
||||||
|
<div class="col-12 mt-2 text-muted">
|
||||||
|
{{ $transaksi->shipping_address }}<br>
|
||||||
|
{{ $transaksi->city }}, {{ $transaksi->province }} {{ $transaksi->postal_code }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Card Produk yang Dipesan --}}
|
||||||
|
<div class="card success-card mt-4">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h5 class="fw-bold mb-4">
|
||||||
|
<i class="fas fa-box me-2 text-success"></i>Produk yang Dipesan
|
||||||
|
</h5>
|
||||||
|
@foreach($transaksi->details as $detail)
|
||||||
|
<div class="d-flex align-items-center mb-3 p-3" style="background: #f8f9fa; border-radius: 10px;">
|
||||||
|
<img src="{{ $detail->product->image_url ?? 'https://via.placeholder.com/80x80/28a745/ffffff?text=Bibit' }}"
|
||||||
|
alt="{{ $detail->product_name }}"
|
||||||
|
class="rounded me-3"
|
||||||
|
style="width: 80px; height: 80px; object-fit: cover;">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h6 class="mb-1">{{ $detail->product_name }}</h6>
|
||||||
|
<p class="text-muted mb-0">{{ number_format($detail->quantity) }} bibit × Rp {{ number_format($detail->price, 0, ',', '.') }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<strong class="text-success">Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span>Subtotal</span>
|
||||||
|
<span>Rp {{ number_format($transaksi->subtotal, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-3">
|
||||||
|
<span>Ongkos Kirim</span>
|
||||||
|
<span>Rp {{ number_format($transaksi->shipping_cost, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<h5 class="fw-bold">Total</h5>
|
||||||
|
<h5 class="fw-bold text-success">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{-- ===== AKHIR AREA PRINT ===== --}}
|
||||||
|
|
||||||
|
{{-- Card Informasi Transfer (tidak ikut print) --}}
|
||||||
|
@if(in_array($transaksi->payment_method, ['qris', 'bri', 'dana', 'seabank', 'shopee']))
|
||||||
<div class="card success-card mt-4">
|
<div class="card success-card mt-4">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<h5 class="fw-bold mb-4">
|
<h5 class="fw-bold mb-4">
|
||||||
<i class="fas fa-box me-2 text-success"></i>Produk yang Dipesan
|
<i class="fas fa-university me-2 text-success"></i>Informasi Pembayaran
|
||||||
</h5>
|
</h5>
|
||||||
|
<div class="alert alert-warning">
|
||||||
@foreach($transaksi->details as $detail)
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
<div class="d-flex align-items-center mb-3 p-3" style="background: #f8f9fa; border-radius: 10px;">
|
Silakan lakukan pembayaran sesuai metode yang Anda pilih
|
||||||
<img src="{{ $detail->product->image_url ?? 'https://via.placeholder.com/80x80/28a745/ffffff?text=Bibit' }}"
|
|
||||||
alt="{{ $detail->product_name }}"
|
|
||||||
class="rounded me-3"
|
|
||||||
style="width: 80px; height: 80px; object-fit: cover;">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h6 class="mb-1">{{ $detail->product_name }}</h6>
|
|
||||||
<p class="text-muted mb-0">{{ number_format($detail->quantity) }} bibit × Rp {{ number_format($detail->price, 0, ',', '.') }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="text-end">
|
|
||||||
<strong class="text-success">Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@if($transaksi->payment_method == 'qris')
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
|
||||||
<span>Subtotal</span>
|
|
||||||
<span>Rp {{ number_format($transaksi->subtotal, 0, ',', '.') }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-3">
|
|
||||||
<span>Ongkos Kirim</span>
|
|
||||||
<span>Rp {{ number_format($transaksi->shipping_cost, 0, ',', '.') }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<h5 class="fw-bold">Total</h5>
|
|
||||||
<h5 class="fw-bold text-success">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Informasi Transfer -->
|
|
||||||
<!-- Informasi Transfer -->
|
|
||||||
@if(in_array($transaksi->payment_method, ['qris', 'bri', 'dana', 'seabank', 'shopee']))
|
|
||||||
<div class="card success-card mt-4">
|
|
||||||
<div class="card-body p-4">
|
|
||||||
<h5 class="fw-bold mb-4">
|
|
||||||
<i class="fas fa-university me-2 text-success"></i>Informasi Pembayaran
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
||||||
Silakan lakukan pembayaran sesuai metode yang Anda pilih
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if($transaksi->payment_method == 'qris')
|
|
||||||
<!-- QRIS Info -->
|
|
||||||
<div class="payment-info">
|
<div class="payment-info">
|
||||||
<p class="mb-1"><strong>📌 Data QRIS:</strong></p>
|
<p class="mb-1"><strong>📌 Data QRIS:</strong></p>
|
||||||
<p class="mb-1">Nama: <strong>ACHMAD BAYU AL GHOZALI</strong></p>
|
<p class="mb-1">Nama: <strong>ACHMAD BAYU AL GHOZALI</strong></p>
|
||||||
<p class="mb-0">NMID: <strong>ID1025440155548</strong></p>
|
<p class="mb-0">NMID: <strong>ID1025440155548</strong></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center my-3">
|
||||||
|
<div id="qrCodeContainer" class="bg-white p-3 d-inline-block rounded shadow-sm">
|
||||||
|
<img src="{{ asset('images/qris.jpg') }}"
|
||||||
|
alt="QRIS DANA"
|
||||||
|
style="width: 400px; height: 400px;"
|
||||||
|
class="border p-2 bg-white">
|
||||||
|
<p class="mb-2"><strong>Total Pembayaran:</strong></p>
|
||||||
|
<h4 class="text-success mb-3">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</h4>
|
||||||
|
<div class="d-flex justify-content-center gap-2 flex-wrap">
|
||||||
|
<span class="badge bg-primary">GoPay</span>
|
||||||
|
<span class="badge bg-info">OVO</span>
|
||||||
|
<span class="badge bg-success">DANA</span>
|
||||||
|
<span class="badge bg-warning text-dark">ShopeePay</span>
|
||||||
|
<span class="badge bg-danger">LinkAja</span>
|
||||||
|
</div>
|
||||||
|
<small class="d-block mt-3 text-muted">
|
||||||
|
Scan QR Code dengan aplikasi pembayaran digital apa saja yang mendukung QRIS
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@elseif($transaksi->payment_method == 'bri')
|
||||||
|
<div class="bank-account">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<div>
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/2/2e/BRI_2020.svg"
|
||||||
|
alt="BRI" style="height: 30px;" class="me-2">
|
||||||
|
<strong>Bank BRI</strong>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('1234567890123456')" title="Copy"></i>
|
||||||
|
</div>
|
||||||
|
<p class="mb-0">No. Rekening: <strong>1234-5678-9012-3456</strong></p>
|
||||||
|
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
||||||
|
</div>
|
||||||
|
@elseif($transaksi->payment_method == 'dana')
|
||||||
|
<div class="bank-account">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<div><strong>💳 DANA</strong></div>
|
||||||
|
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('081234567890')" title="Copy"></i>
|
||||||
|
</div>
|
||||||
|
<p class="mb-0">Nomor DANA: <strong>0812-3456-7890</strong></p>
|
||||||
|
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
||||||
|
</div>
|
||||||
|
@elseif($transaksi->payment_method == 'seabank')
|
||||||
|
<div class="bank-account">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<div><strong>🏦 SeaBank</strong></div>
|
||||||
|
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('901234567890')" title="Copy"></i>
|
||||||
|
</div>
|
||||||
|
<p class="mb-0">No. Rekening: <strong>901234567890</strong></p>
|
||||||
|
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
||||||
|
</div>
|
||||||
|
@elseif($transaksi->payment_method == 'shopee')
|
||||||
|
<div class="bank-account">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<div><strong>🛒 ShopeePay</strong></div>
|
||||||
|
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('081234567890')" title="Copy"></i>
|
||||||
|
</div>
|
||||||
|
<p class="mb-0">Nomor ShopeePay: <strong>0812-3456-7890</strong></p>
|
||||||
|
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="alert alert-info mt-3 mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Setelah pembayaran berhasil, mohon konfirmasi melalui WhatsApp ke <strong>0813-3183-0561</strong> dengan menyertakan <strong>bukti pembayaran</strong> dan <strong>nomor invoice: {{ $transaksi->invoice_number }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center my-3">
|
|
||||||
<!-- QR Code DANA Anda -->
|
|
||||||
<div id="qrCodeContainer" class="bg-white p-3 d-inline-block rounded shadow-sm">
|
|
||||||
<!-- PENTING: Ganti src di bawah dengan QR Code QRIS Dana Anda -->
|
|
||||||
<!-- Opsi 1: Jika Anda punya file QR Code lokal -->
|
|
||||||
<img src="{{ asset('images/qris.jpg') }}"
|
|
||||||
alt="QRIS DANA"
|
|
||||||
style="width: 400px; height: 400px;"
|
|
||||||
class="border p-2 bg-white">
|
|
||||||
<p class="mb-2"><strong>Total Pembayaran:</strong></p>
|
|
||||||
<h4 class="text-success mb-3">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</h4>
|
|
||||||
<div class="d-flex justify-content-center gap-2 flex-wrap">
|
|
||||||
<span class="badge bg-primary">GoPay</span>
|
|
||||||
<span class="badge bg-info">OVO</span>
|
|
||||||
<span class="badge bg-success">DANA</span>
|
|
||||||
<span class="badge bg-warning text-dark">ShopeePay</span>
|
|
||||||
<span class="badge bg-danger">LinkAja</span>
|
|
||||||
</div>
|
|
||||||
<small class="d-block mt-3 text-muted">
|
|
||||||
Scan QR Code dengan aplikasi pembayaran digital apa saja yang mendukung QRIS
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@elseif($transaksi->payment_method == 'bri')
|
|
||||||
<div class="bank-account">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<div>
|
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/2/2e/BRI_2020.svg"
|
|
||||||
alt="BRI" style="height: 30px;" class="me-2">
|
|
||||||
<strong>Bank BRI</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('1234567890123456')" title="Copy"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-0">No. Rekening: <strong>1234-5678-9012-3456</strong></p>
|
@endif
|
||||||
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@elseif($transaksi->payment_method == 'dana')
|
{{-- Card Tombol Aksi (tidak ikut print) --}}
|
||||||
<div class="bank-account">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<div>
|
|
||||||
<strong>💳 DANA</strong>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('081234567890')" title="Copy"></i>
|
|
||||||
</div>
|
|
||||||
<p class="mb-0">Nomor DANA: <strong>0812-3456-7890</strong></p>
|
|
||||||
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@elseif($transaksi->payment_method == 'seabank')
|
|
||||||
<div class="bank-account">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<div>
|
|
||||||
<strong>🏦 SeaBank</strong>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('901234567890')" title="Copy"></i>
|
|
||||||
</div>
|
|
||||||
<p class="mb-0">No. Rekening: <strong>901234567890</strong></p>
|
|
||||||
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@elseif($transaksi->payment_method == 'shopee')
|
|
||||||
<div class="bank-account">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<div>
|
|
||||||
<strong>🛒 ShopeePay</strong>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-copy copy-btn text-success" onclick="copyToClipboard('081234567890')" title="Copy"></i>
|
|
||||||
</div>
|
|
||||||
<p class="mb-0">Nomor ShopeePay: <strong>0812-3456-7890</strong></p>
|
|
||||||
<p class="mb-0 text-muted">a.n. Shop Bibit Cabai Bondowoso</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div class="alert alert-info mt-3 mb-0">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
|
||||||
Setelah pembayaran berhasil, mohon konfirmasi melalui WhatsApp ke <strong>0813-3183-0561</strong> dengan menyertakan <strong>bukti pembayaran</strong> dan <strong>nomor invoice: {{ $transaksi->invoice_number }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
<!-- Actions -->
|
|
||||||
<div class="card success-card mt-4">
|
<div class="card success-card mt-4">
|
||||||
<div class="card-body p-4 text-center">
|
<div class="card-body p-4 text-center">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|
@ -328,11 +376,10 @@ class="border p-2 bg-white">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<button onclick="window.print()" class="btn btn-success w-100">
|
<button onclick="window.print()" class="btn btn-success w-100">
|
||||||
<i class="fas fa-print me-2"></i>Cetak Invoice
|
<i class="fas fa-print me-2"></i>Cetak Bukti Checkout
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<a href="https://wa.me/6282313155053?text=Halo, saya ingin konfirmasi pembayaran dengan invoice {{ $transaksi->invoice_number }}"
|
<a href="https://wa.me/6282313155053?text=Halo, saya ingin konfirmasi pembayaran dengan invoice {{ $transaksi->invoice_number }}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -342,6 +389,7 @@ class="btn btn-success w-100">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,21 +22,21 @@
|
||||||
<div class="contact-info">
|
<div class="contact-info">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5><i class="fas fa-map-marker-alt text-success me-2"></i>Alamat</h5>
|
<h5><i class="fas fa-map-marker-alt text-success me-2"></i>Alamat</h5>
|
||||||
<p class="text-muted">Jl. Raya Bondowoso No. 123<br>Bondowoso, Jawa Timur 68211</p>
|
<p class="text-muted">Jurang sapi, tapen, Bondowoso<br>Masuk paping sebelah toko</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5><i class="fas fa-phone text-success me-2"></i>Telepon</h5>
|
<h5><i class="fas fa-phone text-success me-2"></i>Telepon</h5>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
<a href="tel:+62338421234" class="text-decoration-none">+62 338 421 234</a>
|
<a href="tel:081331830561" class="text-decoration-none">081331830561</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5><i class="fas fa-envelope text-success me-2"></i>Email</h5>
|
<h5><i class="fas fa-envelope text-success me-2"></i>Email</h5>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
<a href="mailto:info@bibitcabaibondowoso.com" class="text-decoration-none">
|
<a href="mailto:bibitcabai@gmail.com" class="text-decoration-none">
|
||||||
info@bibitcabaibondowoso.com
|
bibitcabai@gmail.com
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -44,8 +44,8 @@
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5><i class="fab fa-whatsapp text-success me-2"></i>WhatsApp</h5>
|
<h5><i class="fab fa-whatsapp text-success me-2"></i>WhatsApp</h5>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
<a href="https://wa.me/6281234567890" class="text-decoration-none" target="_blank">
|
<a href="https://wa.me/081331830561" class="text-decoration-none" target="_blank">
|
||||||
+62 812 3456 7890
|
081331830561
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -163,13 +163,13 @@ class="form-control @error('subject') is-invalid @enderror"
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="ratio ratio-21x9">
|
<div class="ratio ratio-21x9">
|
||||||
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3949.3962!2d113.8221!3d-7.9138!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x0!2zN8KwNTQnNDkuNyJTIDExM8KwNDknMTkuNiJF!5e0!3m2!1sen!2sid!4v1640000000000!5m2!1sen!2sid"
|
<iframe src="https://maps.google.com/maps?q=-7.894878,113.909934&z=17&output=embed&hl=id"
|
||||||
style="border:0;"
|
style="border:0;"
|
||||||
allowfullscreen=""
|
allowfullscreen=""
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
referrerpolicy="no-referrer-when-downgrade">
|
referrerpolicy="no-referrer-when-downgrade">
|
||||||
</iframe>
|
</iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 600px; margin: auto; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||||
|
.header { background: #2e7d32; color: white; padding: 20px 30px; }
|
||||||
|
.header h1 { margin: 0; font-size: 22px; }
|
||||||
|
.header p { margin: 5px 0 0; opacity: 0.85; font-size: 14px; }
|
||||||
|
.body { padding: 30px; }
|
||||||
|
.invoice-badge { display: inline-block; background: #e8f5e9; color: #2e7d32; font-weight: bold; padding: 6px 16px; border-radius: 20px; font-size: 14px; margin-bottom: 20px; }
|
||||||
|
.section { margin-bottom: 24px; }
|
||||||
|
.section-title { font-size: 13px; font-weight: bold; text-transform: uppercase; color: #888; letter-spacing: 1px; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 6px; }
|
||||||
|
.info-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 14px; }
|
||||||
|
.info-label { color: #555; }
|
||||||
|
.info-value { font-weight: bold; color: #222; text-align: right; max-width: 60%; }
|
||||||
|
.total-box { background: #f1f8e9; border: 2px solid #a5d6a7; border-radius: 8px; padding: 16px 20px; margin-top: 10px; }
|
||||||
|
.total-row { display: flex; justify-content: space-between; margin-bottom: 6px; font-size: 14px; }
|
||||||
|
.total-final { display: flex; justify-content: space-between; font-size: 18px; font-weight: bold; color: #2e7d32; margin-top: 10px; padding-top: 10px; border-top: 1px dashed #a5d6a7; }
|
||||||
|
.badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: bold; }
|
||||||
|
.badge-pending { background: #fff3cd; color: #856404; }
|
||||||
|
.badge-payment { background: #d1ecf1; color: #0c5460; }
|
||||||
|
.cta { text-align: center; margin-top: 24px; }
|
||||||
|
.cta a { background: #2e7d32; color: white; padding: 12px 30px; border-radius: 6px; text-decoration: none; font-weight: bold; font-size: 15px; display: inline-block; }
|
||||||
|
.footer { background: #f9f9f9; padding: 16px 30px; text-align: center; font-size: 12px; color: #aaa; border-top: 1px solid #eee; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🛒 Ada Pesanan Baru!</h1>
|
||||||
|
<p>{{ $transaksi->created_at->format('d F Y, H:i') }} WIB</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="invoice-badge">{{ $transaksi->invoice_number }}</div>
|
||||||
|
|
||||||
|
<!-- Info Pelanggan -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">👤 Data Pelanggan</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Nama</span>
|
||||||
|
<span class="info-value">{{ $transaksi->customer_name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Telepon</span>
|
||||||
|
<span class="info-value">{{ $transaksi->customer_phone }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Email</span>
|
||||||
|
<span class="info-value">{{ $transaksi->customer_email }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alamat Pengiriman -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">📦 Alamat Pengiriman</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Alamat</span>
|
||||||
|
<span class="info-value">{{ $transaksi->shipping_address }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Kecamatan</span>
|
||||||
|
<span class="info-value">{{ $transaksi->city }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Provinsi</span>
|
||||||
|
<span class="info-value">{{ $transaksi->province ?? 'Jawa Timur' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Kode Pos</span>
|
||||||
|
<span class="info-value">{{ $transaksi->postal_code }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detail Produk -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">🌶️ Detail Produk</div>
|
||||||
|
@foreach($transaksi->details as $detail)
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">{{ $detail->product_name }}</span>
|
||||||
|
<span class="info-value">{{ $detail->quantity }} bibit × Rp {{ number_format($detail->price, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ringkasan Pembayaran -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">💳 Ringkasan Pembayaran</div>
|
||||||
|
<div class="total-box">
|
||||||
|
<div class="total-row">
|
||||||
|
<span>Subtotal</span>
|
||||||
|
<span>Rp {{ number_format($transaksi->subtotal, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
@if($transaksi->discount > 0)
|
||||||
|
<div class="total-row" style="color: #e53935;">
|
||||||
|
<span>Diskon (15%)</span>
|
||||||
|
<span>-Rp {{ number_format($transaksi->discount, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="total-row">
|
||||||
|
<span>Ongkos Kirim</span>
|
||||||
|
<span>{{ $transaksi->shipping_cost == 0 ? 'GRATIS' : 'Rp ' . number_format($transaksi->shipping_cost, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-final">
|
||||||
|
<span>Total Pembayaran</span>
|
||||||
|
<span>Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 12px;">
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Metode Pembayaran</span>
|
||||||
|
<span class="info-value">
|
||||||
|
<span class="badge badge-payment">
|
||||||
|
{{ strtoupper($transaksi->payment_method) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Status Pesanan</span>
|
||||||
|
<span class="info-value">
|
||||||
|
<span class="badge badge-pending">⏳ PENDING</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($transaksi->notes)
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">📝 Catatan Pelanggan</div>
|
||||||
|
<p style="font-size: 14px; color: #444; background: #f9f9f9; padding: 10px; border-radius: 6px;">{{ $transaksi->notes }}</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="cta">
|
||||||
|
<a href="{{ url('/admin/transaksi/' . $transaksi->id) }}">
|
||||||
|
Lihat Detail di Dashboard →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
Shop Bibit Cabai Bondowoso · Notifikasi otomatis, tidak perlu dibalas.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
|
||||||
|
<body style="margin:0;padding:0;background:#f4f4f4;font-family:Arial,sans-serif;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f4;padding:30px 0;">
|
||||||
|
<tr><td align="center">
|
||||||
|
<table width="520" cellpadding="0" cellspacing="0"
|
||||||
|
style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,0.08);">
|
||||||
|
{{-- Header --}}
|
||||||
|
<tr>
|
||||||
|
<td style="background:linear-gradient(135deg,#11998e 0%,#38ef7d 100%);padding:28px;text-align:center;">
|
||||||
|
<h1 style="color:#fff;margin:0;font-size:20px;">🌿 Admin - Bibit Cabai Bondowoso</h1>
|
||||||
|
<p style="color:rgba(255,255,255,0.85);margin:6px 0 0;font-size:13px;">Reset Password Administrator</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{-- Body --}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding:32px 36px;">
|
||||||
|
<p style="color:#333;font-size:15px;margin:0 0 14px;">
|
||||||
|
Halo, <strong>{{ $name }}</strong>!
|
||||||
|
</p>
|
||||||
|
<p style="color:#555;font-size:13px;line-height:1.6;margin:0 0 22px;">
|
||||||
|
Kami menerima permintaan reset password untuk akun <strong>admin</strong> Anda.
|
||||||
|
Gunakan kode berikut:
|
||||||
|
</p>
|
||||||
|
{{-- OTP --}}
|
||||||
|
<div style="text-align:center;margin:0 0 24px;">
|
||||||
|
<div style="display:inline-block;background:#f0fff4;border:2px dashed #11998e;
|
||||||
|
border-radius:12px;padding:18px 36px;">
|
||||||
|
<p style="color:#11998e;font-size:40px;font-weight:bold;
|
||||||
|
letter-spacing:12px;margin:0;font-family:monospace;">{{ $otp }}</p>
|
||||||
|
</div>
|
||||||
|
<p style="color:#888;font-size:12px;margin:10px 0 0;">
|
||||||
|
⏱ Kode berlaku <strong>10 menit</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="background:#fff3cd;border:1px solid #ffc107;border-radius:8px;padding:12px 16px;margin-bottom:16px;">
|
||||||
|
<p style="color:#856404;font-size:12px;margin:0;">
|
||||||
|
⚠️ <strong>Keamanan:</strong> Jangan bagikan kode ini kepada siapapun.
|
||||||
|
Jika Anda tidak meminta reset password, segera amankan akun Anda.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{-- Footer --}}
|
||||||
|
<tr>
|
||||||
|
<td style="background:#f8f9fa;padding:16px 36px;text-align:center;border-top:1px solid #eee;">
|
||||||
|
<p style="color:#aaa;font-size:11px;margin:0;">
|
||||||
|
© {{ date('Y') }} Shop Bibit Cabai Bondowoso — Admin System
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; background: #f4f4f4; padding: 20px; }
|
||||||
|
.container { background: #fff; max-width: 600px; margin: auto; border-radius: 8px; padding: 30px; }
|
||||||
|
.header { background: #198754; color: white; padding: 20px; border-radius: 6px; text-align: center; margin: -30px -30px 25px; }
|
||||||
|
.label { font-weight: bold; color: #198754; }
|
||||||
|
.row { margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 15px; }
|
||||||
|
.message-box { background: #f8f9fa; padding: 15px; border-radius: 6px; border-left: 4px solid #198754; }
|
||||||
|
.footer { text-align: center; color: #aaa; font-size: 12px; margin-top: 20px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h2>🌶️ Shop Bibit Cabai Bondowoso</h2>
|
||||||
|
<p>Pesan Masuk dari Contact Us</p>
|
||||||
|
</div>
|
||||||
|
<div class="row"><span class="label">👤 Nama:</span><p>{{ $data['name'] }}</p></div>
|
||||||
|
<div class="row"><span class="label">📧 Email:</span><p>{{ $data['email'] }}</p></div>
|
||||||
|
<div class="row"><span class="label">📱 No. Telepon:</span><p>{{ $data['phone'] ?? '-' }}</p></div>
|
||||||
|
<div class="row"><span class="label">📌 Subjek:</span><p>{{ $data['subject'] }}</p></div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">💬 Pesan:</span>
|
||||||
|
<div class="message-box">{{ $data['message'] }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">Email dikirim otomatis dari website Shop Bibit Cabai Bondowoso</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 600px; margin: auto; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||||
|
.header { background: linear-gradient(135deg, #28a745, #20c997); color: white; padding: 30px; text-align: center; }
|
||||||
|
.header h1 { margin: 0; font-size: 26px; }
|
||||||
|
.header p { margin: 8px 0 0; opacity: 0.9; }
|
||||||
|
.icon { font-size: 50px; margin-bottom: 10px; }
|
||||||
|
.body { padding: 30px; }
|
||||||
|
.greeting { font-size: 16px; color: #333; margin-bottom: 20px; }
|
||||||
|
.tracking-box { background: #e8f5e9; border: 2px dashed #28a745; border-radius: 10px; padding: 20px; text-align: center; margin: 20px 0; }
|
||||||
|
.tracking-box .label { font-size: 12px; color: #666; text-transform: uppercase; letter-spacing: 1px; }
|
||||||
|
.tracking-box .number { font-size: 22px; font-weight: bold; color: #28a745; font-family: monospace; margin-top: 6px; }
|
||||||
|
.section { margin-bottom: 24px; }
|
||||||
|
.section-title { font-size: 13px; font-weight: bold; text-transform: uppercase; color: #888; letter-spacing: 1px; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 6px; }
|
||||||
|
.info-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 14px; }
|
||||||
|
.info-label { color: #555; }
|
||||||
|
.info-value { font-weight: bold; color: #222; text-align: right; }
|
||||||
|
.total-box { background: #f1f8e9; border: 2px solid #a5d6a7; border-radius: 8px; padding: 16px 20px; }
|
||||||
|
.total-final { display: flex; justify-content: space-between; font-size: 18px; font-weight: bold; color: #28a745; }
|
||||||
|
.steps { margin: 20px 0; }
|
||||||
|
.step { display: flex; align-items: flex-start; margin-bottom: 14px; }
|
||||||
|
.step-num { background: #28a745; color: white; border-radius: 50%; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: bold; flex-shrink: 0; margin-right: 12px; margin-top: 2px; }
|
||||||
|
.step-text { font-size: 14px; color: #444; }
|
||||||
|
.footer { background: #f9f9f9; padding: 16px 30px; text-align: center; font-size: 12px; color: #aaa; border-top: 1px solid #eee; }
|
||||||
|
.whatsapp-btn { display: inline-block; background: #25D366; color: white; padding: 12px 28px; border-radius: 25px; text-decoration: none; font-weight: bold; margin-top: 16px; font-size: 15px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="icon">📦</div>
|
||||||
|
<h1>Pesanan Sedang Dikirim!</h1>
|
||||||
|
<p>{{ $transaksi->created_at->format('d F Y') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
|
||||||
|
<p class="greeting">
|
||||||
|
Halo <strong>{{ $transaksi->customer_name }}</strong>, kabar baik! 🎉<br>
|
||||||
|
Pesanan Anda dengan invoice <strong>{{ $transaksi->invoice_number }}</strong>
|
||||||
|
sudah dalam perjalanan menuju alamat Anda.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{-- Tracking Number --}}
|
||||||
|
@if($transaksi->tracking_number)
|
||||||
|
<div class="tracking-box">
|
||||||
|
<div class="label">Nomor Resi Pengiriman</div>
|
||||||
|
<div class="number">{{ $transaksi->tracking_number }}</div>
|
||||||
|
<div style="font-size:12px; color:#666; margin-top:8px;">
|
||||||
|
Simpan nomor resi ini untuk melacak paket Anda
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Detail Pesanan --}}
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">🌶️ Detail Pesanan</div>
|
||||||
|
@foreach($transaksi->details as $detail)
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">{{ $detail->product_name }}</span>
|
||||||
|
<span class="info-value">{{ $detail->quantity }} bibit</span>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Alamat Pengiriman --}}
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">📍 Dikirim ke</div>
|
||||||
|
<div style="font-size:14px; color:#444; line-height:1.8;">
|
||||||
|
{{ $transaksi->shipping_address }}<br>
|
||||||
|
Kec. {{ $transaksi->city }}, {{ $transaksi->province }}<br>
|
||||||
|
Kode Pos: {{ $transaksi->postal_code }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Total --}}
|
||||||
|
<div class="total-box">
|
||||||
|
<div class="total-final">
|
||||||
|
<span>Total Pembayaran</span>
|
||||||
|
<span>Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Instruksi --}}
|
||||||
|
<div class="section" style="margin-top:24px;">
|
||||||
|
<div class="section-title">📋 Apa yang harus dilakukan?</div>
|
||||||
|
<div class="steps">
|
||||||
|
<div class="step">
|
||||||
|
<div class="step-num">1</div>
|
||||||
|
<div class="step-text">Pantau pengiriman menggunakan nomor resi di atas</div>
|
||||||
|
</div>
|
||||||
|
<div class="step">
|
||||||
|
<div class="step-num">2</div>
|
||||||
|
<div class="step-text">Pastikan ada orang di rumah untuk menerima paket</div>
|
||||||
|
</div>
|
||||||
|
<div class="step">
|
||||||
|
<div class="step-num">3</div>
|
||||||
|
<div class="step-text">Periksa kondisi paket saat diterima sebelum menandatangani</div>
|
||||||
|
</div>
|
||||||
|
<div class="step">
|
||||||
|
<div class="step-num">4</div>
|
||||||
|
<div class="step-text">Jika ada kendala, hubungi kami via WhatsApp</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align:center;">
|
||||||
|
<a href="https://wa.me/62081331830561?text=Halo, saya ingin menanyakan pesanan {{ $transaksi->invoice_number }}"
|
||||||
|
class="whatsapp-btn">
|
||||||
|
💬 Hubungi via WhatsApp
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
Shop Bibit Cabai Bondowoso · 081331830561<br>
|
||||||
|
Email ini dikirim otomatis, tidak perlu dibalas.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Kode Verifikasi Reset Password</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:0;background-color:#f4f4f4;font-family:Arial,sans-serif;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f4;padding:30px 0;">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table width="560" cellpadding="0" cellspacing="0"
|
||||||
|
style="background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,0.08);">
|
||||||
|
|
||||||
|
{{-- Header --}}
|
||||||
|
<tr>
|
||||||
|
<td style="background:#198754;padding:30px;text-align:center;">
|
||||||
|
<h1 style="color:#ffffff;margin:0;font-size:22px;">🌱 Bibit Cabai Bondowoso</h1>
|
||||||
|
<p style="color:rgba(255,255,255,0.85);margin:8px 0 0;font-size:14px;">
|
||||||
|
Reset Password
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{{-- Body --}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding:36px 40px;">
|
||||||
|
<p style="color:#333;font-size:16px;margin:0 0 16px;">
|
||||||
|
Halo, <strong>{{ $name }}</strong>!
|
||||||
|
</p>
|
||||||
|
<p style="color:#555;font-size:14px;line-height:1.6;margin:0 0 24px;">
|
||||||
|
Kami menerima permintaan reset password untuk akun Anda.
|
||||||
|
Gunakan kode berikut untuk memverifikasi identitas Anda:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{-- OTP Box --}}
|
||||||
|
<div style="text-align:center;margin:0 0 28px;">
|
||||||
|
<div style="display:inline-block;background:#f0fff4;border:2px dashed #198754;
|
||||||
|
border-radius:12px;padding:20px 40px;">
|
||||||
|
<p style="color:#198754;font-size:42px;font-weight:bold;
|
||||||
|
letter-spacing:12px;margin:0;font-family:monospace;">
|
||||||
|
{{ $otp }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p style="color:#888;font-size:13px;margin:12px 0 0;">
|
||||||
|
⏱ Kode ini berlaku selama <strong>10 menit</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="color:#555;font-size:14px;line-height:1.6;margin:0 0 12px;">
|
||||||
|
Jika Anda tidak meminta reset password, abaikan email ini.
|
||||||
|
Akun Anda tetap aman.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{{-- Footer --}}
|
||||||
|
<tr>
|
||||||
|
<td style="background:#f8f9fa;padding:20px 40px;text-align:center;border-top:1px solid #eee;">
|
||||||
|
<p style="color:#aaa;font-size:12px;margin:0;">
|
||||||
|
© {{ date('Y') }} Shop Bibit Cabai Bondowoso. Semua hak dilindungi.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 text-center">
|
<div class="col-lg-6 text-center">
|
||||||
<img src="{{ asset('images/cabai-hero.jpg') }}"
|
<img src="{{ asset('images/bara f1.jpeg') }}"
|
||||||
alt="Bibit Cabai"
|
alt="Bibit Cabai"
|
||||||
class="img-fluid hero-image"
|
class="img-fluid hero-image"
|
||||||
onerror="this.src='https://via.placeholder.com/400x300/28a745/ffffff?text=Bibit+Cabai+Hero'">
|
onerror="this.src='https://via.placeholder.com/400x300/28a745/ffffff?text=Bibit+Cabai+Hero'">
|
||||||
|
|
@ -102,7 +102,7 @@ class="img-fluid hero-image"
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<img src="{{ $product->image_url }}"
|
<img src="{{ $product->image ? asset('storage/' . $product->image) : 'https://via.placeholder.com/300x250/28a745/ffffff?text=' . urlencode($product->name) }}"
|
||||||
class="card-img-top product-image"
|
class="card-img-top product-image"
|
||||||
alt="{{ $product->name }}"
|
alt="{{ $product->name }}"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
|
@ -111,7 +111,7 @@ class="card-img-top product-image"
|
||||||
|
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<h5 class="card-title">{{ $product->name }}</h5>
|
<h5 class="card-title">{{ $product->name }}</h5>
|
||||||
<div class="price-text mb-3">{{ $product->formatted_price }}</div>
|
<div class="price-text mb-3">Rp {{ number_format($product->price, 0, ',', '.') }}</div>
|
||||||
|
|
||||||
@if($product->total_sold > 0)
|
@if($product->total_sold > 0)
|
||||||
<small class="text-muted mb-3">
|
<small class="text-muted mb-3">
|
||||||
|
|
@ -148,6 +148,10 @@ class="card-img-top product-image"
|
||||||
<i class="fas fa-shopping-cart me-1"></i>
|
<i class="fas fa-shopping-cart me-1"></i>
|
||||||
Beli Sekarang
|
Beli Sekarang
|
||||||
</button>
|
</button>
|
||||||
|
<a href="{{ route('products.show', $product->id) }}" class="btn btn-outline-success w-100 mt-2">
|
||||||
|
<i class="fas fa-eye me-1"></i>
|
||||||
|
Lihat Detail Produk
|
||||||
|
</a>
|
||||||
@else
|
@else
|
||||||
<button class="btn btn-secondary w-100" disabled>
|
<button class="btn btn-secondary w-100" disabled>
|
||||||
<i class="fas fa-ban me-1"></i>
|
<i class="fas fa-ban me-1"></i>
|
||||||
|
|
@ -194,7 +198,77 @@ class="card-img-top product-image"
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- Semua Produk Section -->
|
||||||
|
<section class="py-5 bg-white">
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h2 class="display-5 fw-bold text-success">SEMUA PRODUK</h2>
|
||||||
|
<p class="lead text-muted">Pilihan lengkap bibit cabai kami</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
@foreach($allProducts as $product)
|
||||||
|
<div class="col-lg-4 col-md-6">
|
||||||
|
<div class="card product-card h-100 position-relative">
|
||||||
|
<div class="overflow-hidden">
|
||||||
|
<img src="{{ $product->image ? asset('storage/' . $product->image) : 'https://via.placeholder.com/300x250/28a745/ffffff?text=' . urlencode($product->name) }}"
|
||||||
|
class="card-img-top product-image"
|
||||||
|
alt="{{ $product->name }}"
|
||||||
|
loading="lazy">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<h5 class="card-title">{{ $product->name }}</h5>
|
||||||
|
<div class="price-text mb-3">Rp {{ number_format($product->price, 0, ',', '.') }}</div>
|
||||||
|
|
||||||
|
<div class="mt-auto">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-boxes me-1"></i>
|
||||||
|
Stok: {{ number_format($product->stock) }}
|
||||||
|
</small>
|
||||||
|
@if($product->stock > 0)
|
||||||
|
<span class="badge bg-success">Tersedia</span>
|
||||||
|
@else
|
||||||
|
<span class="badge bg-danger">Habis</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($product->stock > 0)
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<button class="btn btn-outline-success" type="button" onclick="decreaseQty('all-{{ $product->id }}')">
|
||||||
|
<i class="fas fa-minus"></i>
|
||||||
|
</button>
|
||||||
|
<input type="number" class="form-control quantity-input" id="qty-all-{{ $product->id }}" value="1" min="1" max="{{ $product->stock }}">
|
||||||
|
<button class="btn btn-outline-success" type="button" onclick="increaseQty('all-{{ $product->id }}', {{ $product->stock }})">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-success w-100" onclick="checkoutAll({{ $product->id }}, 'all-{{ $product->id }}')">
|
||||||
|
<i class="fas fa-shopping-cart me-1"></i>
|
||||||
|
Beli Sekarang
|
||||||
|
</button>
|
||||||
|
<a href="{{ route('products.show', $product->id) }}" class="btn btn-outline-success w-100 mt-2">
|
||||||
|
<i class="fas fa-eye me-1"></i>
|
||||||
|
Lihat Detail Produk
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<button class="btn btn-secondary w-100" disabled>
|
||||||
|
<i class="fas fa-ban me-1"></i>
|
||||||
|
Stok Habis
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Features Section -->
|
||||||
<!-- Features Section -->
|
<!-- Features Section -->
|
||||||
<section class="py-5 bg-white">
|
<section class="py-5 bg-white">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
@ -214,10 +288,10 @@ class="card-img-top product-image"
|
||||||
<i class="fas fa-truck fa-2x"></i>
|
<i class="fas fa-truck fa-2x"></i>
|
||||||
</div>
|
</div>
|
||||||
<h5>Pengiriman Cepat</h5>
|
<h5>Pengiriman Cepat</h5>
|
||||||
<p class="text-muted">Pengiriman ke seluruh Indonesia dengan packaging aman.</p>
|
<p class="text-muted">Pengiriman ke seluruh kabupaten Bondowoso dengan packaging aman.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-3 col-md-6">
|
<!-- <div class="col-lg-3 col-md-6">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="bg-success text-white rounded-circle mx-auto mb-3 d-flex align-items-center justify-content-center" style="width: 80px; height: 80px;">
|
<div class="bg-success text-white rounded-circle mx-auto mb-3 d-flex align-items-center justify-content-center" style="width: 80px; height: 80px;">
|
||||||
<i class="fas fa-medal fa-2x"></i>
|
<i class="fas fa-medal fa-2x"></i>
|
||||||
|
|
@ -225,7 +299,7 @@ class="card-img-top product-image"
|
||||||
<h5>Garansi Tumbuh</h5>
|
<h5>Garansi Tumbuh</h5>
|
||||||
<p class="text-muted">Garansi bibit tumbuh atau uang kembali 100%.</p>
|
<p class="text-muted">Garansi bibit tumbuh atau uang kembali 100%.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="col-lg-3 col-md-6">
|
<div class="col-lg-3 col-md-6">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="bg-success text-white rounded-circle mx-auto mb-3 d-flex align-items-center justify-content-center" style="width: 80px; height: 80px;">
|
<div class="bg-success text-white rounded-circle mx-auto mb-3 d-flex align-items-center justify-content-center" style="width: 80px; height: 80px;">
|
||||||
|
|
@ -241,6 +315,10 @@ class="card-img-top product-image"
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Cek status login dari Laravel (true/false)
|
||||||
|
const isLoggedIn = @json(auth()->check());
|
||||||
|
const loginUrl = "{{ route('login') }}";
|
||||||
|
|
||||||
function increaseQty(productId, maxStock) {
|
function increaseQty(productId, maxStock) {
|
||||||
const input = document.getElementById('qty-' + productId);
|
const input = document.getElementById('qty-' + productId);
|
||||||
let value = parseInt(input.value);
|
let value = parseInt(input.value);
|
||||||
|
|
@ -258,8 +336,21 @@ function decreaseQty(productId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkout(productId) {
|
function checkout(productId) {
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
window.location.href = loginUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const quantity = document.getElementById('qty-' + productId).value;
|
const quantity = document.getElementById('qty-' + productId).value;
|
||||||
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkoutAll(productId, inputKey) {
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
window.location.href = loginUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const quantity = document.getElementById('qty-' + inputKey).value;
|
||||||
|
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
@ -97,41 +97,45 @@
|
||||||
|
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
@auth
|
@auth
|
||||||
<!-- Admin Dashboard Access -->
|
{{-- Tampilkan Admin Panel HANYA jika is_admin = 1 --}}
|
||||||
<li class="nav-item me-2">
|
@if(Auth::user()->is_admin)
|
||||||
<a class="nav-link admin-link admin-notification px-3 py-2 rounded"
|
<li class="nav-item me-2">
|
||||||
href="{{ route('admin.dashboard') }}"
|
<a class="nav-link admin-link admin-notification px-3 py-2 rounded"
|
||||||
title="Dashboard Admin">
|
href="{{ route('admin.dashboard') }}"
|
||||||
<i class="fas fa-crown me-1"></i>Admin Panel
|
title="Dashboard Admin">
|
||||||
<span class="badge admin-badge">NEW</span>
|
<i class="fas fa-crown me-1"></i>Admin Panel
|
||||||
</a>
|
<span class="badge admin-badge">NEW</span>
|
||||||
</li>
|
</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle text-white" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle text-white" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="fas fa-user me-1"></i>{{ Auth::user()->name }}
|
<i class="fas fa-user me-1"></i>{{ Auth::user()->name }}
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||||
|
@if(Auth::user()->is_admin)
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item admin-access" href="{{ route('admin.dashboard') }}">
|
<a class="dropdown-item admin-access" href="{{ route('admin.dashboard') }}">
|
||||||
<i class="fas fa-tachometer-alt me-2"></i>Dashboard Admin
|
<i class="fas fa-tachometer-alt me-2"></i>Dashboard Admin
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
@endif
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="#">
|
<a class="dropdown-item" href="{{ route('profile') }}">
|
||||||
<i class="fas fa-user-circle me-2"></i>Profil Saya
|
<i class="fas fa-user-circle me-2"></i>Profil Saya
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="#">
|
<a class="dropdown-item" href="{{ route('orders.my') }}">
|
||||||
<i class="fas fa-box me-2"></i>Pesanan Saya
|
<i class="fas fa-box me-2"></i>Pesanan Saya
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="#">
|
<!-- <a class="dropdown-item" href="#">
|
||||||
<i class="fas fa-cog me-2"></i>Pengaturan
|
<i class="fas fa-cog me-2"></i>Pengaturan
|
||||||
</a>
|
</a> -->
|
||||||
</li>
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li>
|
<li>
|
||||||
|
|
@ -171,6 +175,7 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Alert Messages -->
|
<!-- Alert Messages -->
|
||||||
|
<!-- Alert Messages -->
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
|
@ -185,9 +190,29 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<!-- Notifikasi Pesanan Customer -->
|
||||||
|
@auth
|
||||||
|
@php
|
||||||
|
$notifTerkirim = auth()->user()->unreadNotifications
|
||||||
|
->filter(fn($n) => isset($n->data['status']) && in_array($n->data['status'], ['processing', 'shipped', 'delivered', 'cancelled']))
|
||||||
|
->count();
|
||||||
|
@endphp
|
||||||
|
@if($notifTerkirim > 0)
|
||||||
|
<div id="notif-pesanan" data-count="{{ $notifTerkirim }}" class="alert alert-warning alert-dismissible fade show mb-0" role="alert">
|
||||||
|
<div class="container">
|
||||||
|
<i class="fas fa-bell me-2"></i>
|
||||||
|
Anda memiliki <strong>{{ $notifTerkirim }}</strong> update status pesanan baru.
|
||||||
|
<a href="{{ route('orders.my') }}" class="alert-link">Lihat Pesanan Saya</a>
|
||||||
|
<button type="button" class="btn-close" onclick="tutupNotif('notif_pesanan', 'notif-pesanan')"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endauth
|
||||||
|
|
||||||
<!-- Admin Welcome Banner (for authenticated users) -->
|
<!-- Admin Welcome Banner (for authenticated users) -->
|
||||||
@auth
|
@auth
|
||||||
<div class="alert alert-info alert-dismissible fade show mb-0" role="alert" style="background: linear-gradient(135deg, #17a2b8, #20c997); border: none;">
|
@if(Auth::user()->is_admin)
|
||||||
|
<div id="notif-welcome" class="alert alert-info alert-dismissible fade show mb-0" role="alert" style="background: linear-gradient(135deg, #17a2b8, #20c997); border: none;">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap">
|
<div class="d-flex align-items-center justify-content-between flex-wrap">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
|
|
@ -198,11 +223,13 @@
|
||||||
<a href="{{ route('admin.dashboard') }}" class="btn btn-light btn-sm me-2">
|
<a href="{{ route('admin.dashboard') }}" class="btn btn-light btn-sm me-2">
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Buka Dashboard
|
<i class="fas fa-external-link-alt me-1"></i>Buka Dashboard
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close btn-close-white" onclick="tutupNotif('notif_welcome', 'notif-welcome')"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@endauth
|
@endauth
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
|
|
@ -218,29 +245,40 @@
|
||||||
<h5><i class="fas fa-seedling me-2"></i>Bibit Cabai</h5>
|
<h5><i class="fas fa-seedling me-2"></i>Bibit Cabai</h5>
|
||||||
<p>Platform terbaik untuk kebutuhan bibit dan tanaman Anda.</p>
|
<p>Platform terbaik untuk kebutuhan bibit dan tanaman Anda.</p>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
@auth
|
@auth
|
||||||
<a href="{{ route('admin.dashboard') }}" class="btn btn-outline-success btn-sm">
|
@if(Auth::user()->is_admin)
|
||||||
<i class="fas fa-tools me-1"></i>Admin Panel
|
<a href="{{ route('admin.dashboard') }}" class="btn btn-outline-success btn-sm">
|
||||||
</a>
|
<i class="fas fa-tools me-1"></i>Admin Panel
|
||||||
@else
|
</a>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
<a href="{{ route('admin.login.form') }}" class="btn btn-outline-success btn-sm">
|
<a href="{{ route('admin.login.form') }}" class="btn btn-outline-success btn-sm">
|
||||||
<i class="fas fa-tools me-1"></i>Admin Panel
|
<i class="fas fa-tools me-1"></i>Admin Panel
|
||||||
</a>
|
</a>
|
||||||
@endauth
|
@endauth
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h5>Hubungi Kami</h5>
|
<h5>Hubungi Kami</h5>
|
||||||
<p><i class="fas fa-envelope me-2"></i>info@tabibit.com</p>
|
<p>
|
||||||
<p><i class="fas fa-phone me-2"></i>+62 123 456 789</p>
|
<a href="https://wa.me/62123456789" target="_blank" class="text-white text-decoration-none">
|
||||||
<p><i class="fas fa-shield-alt me-2"></i>Admin: admin@tabibit.com</p>
|
<i class="fab fa-whatsapp me-2 text-success"></i>+62 123 456 789
|
||||||
</div>
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="mailto:info@tabibit.com" class="text-white text-decoration-none">
|
||||||
|
<i class="fas fa-envelope me-2 text-success"></i>info@tabibit.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p>© {{ date('Y') }} Bibit Cabai. All rights reserved. |
|
<p>© {{ date('Y') }} Bibit Cabai. All rights reserved. |
|
||||||
@auth
|
@auth
|
||||||
<a href="{{ route('admin.dashboard') }}" class="text-success text-decoration-none">Admin Panel</a>
|
@if(Auth::user()->is_admin)
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="text-success text-decoration-none">Admin Panel</a>
|
||||||
|
@endif
|
||||||
@else
|
@else
|
||||||
<a href="{{ route('admin.login.form') }}" class="text-success text-decoration-none">Admin Panel</a>
|
<a href="{{ route('admin.login.form') }}" class="text-success text-decoration-none">Admin Panel</a>
|
||||||
@endauth
|
@endauth
|
||||||
|
|
@ -253,31 +291,53 @@
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<!-- Custom Scripts -->
|
<!-- Custom Scripts -->
|
||||||
<script>
|
<script>
|
||||||
// Auto-hide alerts
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
// Notif pesanan - cek berdasarkan jumlah notif
|
||||||
const alerts = document.querySelectorAll('.alert:not(.alert-info)');
|
var pesananEl = document.getElementById('notif-pesanan');
|
||||||
alerts.forEach(function(alert) {
|
if (pesananEl) {
|
||||||
setTimeout(function() {
|
var count = pesananEl.getAttribute('data-count');
|
||||||
if (alert.parentNode) {
|
var savedCount = localStorage.getItem('notif_pesanan_count');
|
||||||
const bsAlert = new bootstrap.Alert(alert);
|
if (savedCount === count) {
|
||||||
bsAlert.close();
|
pesananEl.style.display = 'none';
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hide admin banner after 10 seconds
|
|
||||||
const adminBanner = document.querySelector('.alert-info');
|
|
||||||
if (adminBanner) {
|
|
||||||
setTimeout(function() {
|
|
||||||
if (adminBanner.parentNode) {
|
|
||||||
const bsAlert = new bootstrap.Alert(adminBanner);
|
|
||||||
bsAlert.close();
|
|
||||||
}
|
|
||||||
}, 10000);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
</script>
|
|
||||||
|
// Notif welcome - pakai sessionStorage (reset setiap login baru)
|
||||||
|
if (sessionStorage.getItem('notif_welcome') === 'ditutup') {
|
||||||
|
var el = document.getElementById('notif-welcome');
|
||||||
|
if (el) el.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-hide semua notif setelah 5 detik
|
||||||
|
setTimeout(function() {
|
||||||
|
['notif-pesanan', 'notif-welcome'].forEach(function(elId) {
|
||||||
|
var el = document.getElementById(elId);
|
||||||
|
if (el && el.style.display !== 'none') {
|
||||||
|
el.style.transition = 'opacity 0.8s ease';
|
||||||
|
el.style.opacity = '0';
|
||||||
|
setTimeout(function() { el.style.display = 'none'; }, 800);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function tutupNotif(key, elId) {
|
||||||
|
if (key === 'notif_pesanan') {
|
||||||
|
var el = document.getElementById(elId);
|
||||||
|
var count = el ? el.getAttribute('data-count') : '0';
|
||||||
|
localStorage.setItem('notif_pesanan_count', count);
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem(key, 'ditutup'); // ganti localStorage -> sessionStorage
|
||||||
|
}
|
||||||
|
var el = document.getElementById(elId);
|
||||||
|
if (el) {
|
||||||
|
el.style.transition = 'opacity 0.3s';
|
||||||
|
el.style.opacity = '0';
|
||||||
|
setTimeout(function() { el.style.display = 'none'; }, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
@yield('scripts')
|
@yield('scripts')
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -96,37 +96,41 @@ class="card-img-top"
|
||||||
|
|
||||||
<!-- Quantity Form -->
|
<!-- Quantity Form -->
|
||||||
<div class="mt-auto">
|
<div class="mt-auto">
|
||||||
@if($product->stock > 0)
|
@if($product->stock > 0)
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<button class="btn btn-outline-success"
|
<button class="btn btn-outline-success"
|
||||||
type="button"
|
type="button"
|
||||||
onclick="decreaseQty({{ $product->id }})">
|
onclick="decreaseQty({{ $product->id }})">
|
||||||
<i class="fas fa-minus"></i>
|
<i class="fas fa-minus"></i>
|
||||||
</button>
|
</button>
|
||||||
<input type="number"
|
<input type="number"
|
||||||
class="form-control quantity-input text-center"
|
class="form-control quantity-input text-center"
|
||||||
id="qty-{{ $product->id }}"
|
id="qty-{{ $product->id }}"
|
||||||
value="1"
|
value="1"
|
||||||
min="1"
|
min="1"
|
||||||
max="{{ $product->stock }}">
|
max="{{ $product->stock }}">
|
||||||
<button class="btn btn-outline-success"
|
<button class="btn btn-outline-success"
|
||||||
type="button"
|
type="button"
|
||||||
onclick="increaseQty({{ $product->id }}, {{ $product->stock }})">
|
onclick="increaseQty({{ $product->id }}, {{ $product->stock }})">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-success w-100"
|
<button class="btn btn-success w-100"
|
||||||
onclick="checkout({{ $product->id }})">
|
onclick="checkout({{ $product->id }})">
|
||||||
<i class="fas fa-shopping-cart me-1"></i>
|
<i class="fas fa-shopping-cart me-1"></i>
|
||||||
Beli Sekarang
|
Beli Sekarang
|
||||||
</button>
|
</button>
|
||||||
@else
|
<a href="{{ route('products.show', $product->id) }}" class="btn btn-outline-success w-100 mt-2">
|
||||||
<button class="btn btn-secondary w-100" disabled>
|
<i class="fas fa-eye me-1"></i>
|
||||||
<i class="fas fa-ban me-1"></i>
|
Lihat Detail Produk
|
||||||
Stok Habis
|
</a>
|
||||||
</button>
|
@else
|
||||||
@endif
|
<button class="btn btn-secondary w-100" disabled>
|
||||||
|
<i class="fas fa-ban me-1"></i>
|
||||||
|
Stok Habis
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -150,6 +154,59 @@ class="form-control quantity-input text-center"
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Script dipindah ke dalam @section('content') agar terbaca oleh browser --}}
|
||||||
|
<script>
|
||||||
|
const isLoggedIn = @json(auth()->check());
|
||||||
|
const loginUrl = "{{ route('login') }}";
|
||||||
|
|
||||||
|
function increaseQty(productId, maxStock) {
|
||||||
|
const input = document.getElementById('qty-' + productId);
|
||||||
|
let value = parseInt(input.value) || 1;
|
||||||
|
if (value < maxStock) {
|
||||||
|
input.value = value + 1;
|
||||||
|
} else {
|
||||||
|
alert('Stok maksimal: ' + maxStock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decreaseQty(productId) {
|
||||||
|
const input = document.getElementById('qty-' + productId);
|
||||||
|
let value = parseInt(input.value) || 1;
|
||||||
|
if (value > 1) {
|
||||||
|
input.value = value - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkout(productId) {
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
window.location.href = loginUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = document.getElementById('qty-' + productId);
|
||||||
|
if (!input) {
|
||||||
|
alert('Terjadi kesalahan, coba refresh halaman!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quantity = parseInt(input.value) || 1;
|
||||||
|
const max = parseInt(input.getAttribute('max'));
|
||||||
|
|
||||||
|
if (isNaN(quantity) || quantity < 1) {
|
||||||
|
alert('Jumlah minimal 1!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantity > max) {
|
||||||
|
alert('Jumlah melebihi stok tersedia (' + max + ')!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = '/checkout?product_id=' + productId + '&quantity=' + quantity;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('styles')
|
@push('styles')
|
||||||
|
|
@ -244,28 +301,3 @@ class="form-control quantity-input text-center"
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script>
|
|
||||||
function increaseQty(productId, maxStock) {
|
|
||||||
const input = document.getElementById('qty-' + productId);
|
|
||||||
let value = parseInt(input.value);
|
|
||||||
if (value < maxStock) {
|
|
||||||
input.value = value + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function decreaseQty(productId) {
|
|
||||||
const input = document.getElementById('qty-' + productId);
|
|
||||||
let value = parseInt(input.value);
|
|
||||||
if (value > 1) {
|
|
||||||
input.value = value - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkout(productId) {
|
|
||||||
const quantity = document.getElementById('qty-' + productId).value;
|
|
||||||
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@endpush
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', $product->name . ' - Shop Bibit Cabai Bondowoso')
|
||||||
|
|
||||||
|
@section('styles')
|
||||||
|
<style>
|
||||||
|
.product-detail-image {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 450px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
.price-text {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
.info-card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 15px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
.quantity-input {
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.badge-stock {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container py-5">
|
||||||
|
|
||||||
|
{{-- Breadcrumb --}}
|
||||||
|
<nav aria-label="breadcrumb" class="mb-4">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}" class="text-success">Home</a></li>
|
||||||
|
<li class="breadcrumb-item active">{{ $product->name }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="row g-5">
|
||||||
|
{{-- Gambar Produk --}}
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<img src="{{ $product->image ? asset('storage/' . $product->image) : 'https://via.placeholder.com/500x450/28a745/ffffff?text=' . urlencode($product->name) }}"
|
||||||
|
alt="{{ $product->name }}"
|
||||||
|
class="product-detail-image"
|
||||||
|
onerror="this.src='https://via.placeholder.com/500x450/28a745/ffffff?text={{ urlencode($product->name) }}'">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Info Produk --}}
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h1 class="fw-bold mb-2">{{ $product->name }}</h1>
|
||||||
|
|
||||||
|
@if($product->category)
|
||||||
|
<span class="badge bg-success-subtle text-success border border-success mb-3">
|
||||||
|
{{ $product->category }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="price-text mb-3">
|
||||||
|
Rp {{ number_format($product->price, 0, ',', '.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Status Stok --}}
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-4">
|
||||||
|
<span class="text-muted">
|
||||||
|
<i class="fas fa-boxes me-1"></i>Stok: <strong>{{ number_format($product->stock) }}</strong>
|
||||||
|
</span>
|
||||||
|
@if($product->stock > 0)
|
||||||
|
<span class="badge bg-success badge-stock">Tersedia</span>
|
||||||
|
@else
|
||||||
|
<span class="badge bg-danger badge-stock">Stok Habis</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Total Terjual --}}
|
||||||
|
@if(isset($product->sold) && $product->sold > 0)
|
||||||
|
<p class="text-muted mb-4">
|
||||||
|
<i class="fas fa-chart-line me-1 text-success"></i>
|
||||||
|
Terjual: <strong>{{ number_format($product->sold) }} bibit</strong>
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Aksi Beli --}}
|
||||||
|
@if($product->stock > 0)
|
||||||
|
<div class="card info-card p-4 mb-4">
|
||||||
|
<label class="form-label fw-semibold">Jumlah</label>
|
||||||
|
<div class="input-group mb-3" style="max-width: 200px;">
|
||||||
|
<button class="btn btn-outline-success" type="button" onclick="decreaseQty()">
|
||||||
|
<i class="fas fa-minus"></i>
|
||||||
|
</button>
|
||||||
|
<input type="number" class="form-control quantity-input" id="qty-detail"
|
||||||
|
value="1" min="1" max="{{ $product->stock }}">
|
||||||
|
<button class="btn btn-outline-success" type="button" onclick="increaseQty({{ $product->stock }})">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success btn-lg w-100" onclick="checkout({{ $product->id }})">
|
||||||
|
<i class="fas fa-shopping-cart me-2"></i>Beli Sekarang
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<button class="btn btn-secondary btn-lg w-100 mb-4" disabled>
|
||||||
|
<i class="fas fa-ban me-2"></i>Stok Habis
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Deskripsi --}}
|
||||||
|
@if($product->description)
|
||||||
|
<div class="card info-card p-4">
|
||||||
|
<h5 class="fw-bold mb-3"><i class="fas fa-info-circle me-2 text-success"></i>Deskripsi Produk</h5>
|
||||||
|
<p class="text-muted mb-0" style="line-height: 1.8;">{{ $product->description }}</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Tombol Kembali --}}
|
||||||
|
<div class="mt-5">
|
||||||
|
<a href="{{ route('home') }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Beranda
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const isLoggedIn = @json(auth()->check());
|
||||||
|
const loginUrl = "{{ route('login') }}";
|
||||||
|
|
||||||
|
function increaseQty(maxStock) {
|
||||||
|
const input = document.getElementById('qty-detail');
|
||||||
|
if (parseInt(input.value) < maxStock) input.value = parseInt(input.value) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decreaseQty() {
|
||||||
|
const input = document.getElementById('qty-detail');
|
||||||
|
if (parseInt(input.value) > 1) input.value = parseInt(input.value) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkout(productId) {
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
window.location.href = loginUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const quantity = document.getElementById('qty-detail').value;
|
||||||
|
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|
@ -0,0 +1,297 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Batalkan Pesanan - Bibit Cabai')
|
||||||
|
|
||||||
|
@section('styles')
|
||||||
|
<style>
|
||||||
|
body { background: #f8f9fa; font-family: 'Segoe UI', sans-serif; }
|
||||||
|
|
||||||
|
.page-hero {
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
padding: 35px 0; color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-wrap {
|
||||||
|
max-width: 680px; margin: 0 auto; padding: 30px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card {
|
||||||
|
background: white; border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.07);
|
||||||
|
padding: 25px; margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1rem; font-weight: 700; color: #333;
|
||||||
|
margin-bottom: 18px; padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-box {
|
||||||
|
background: #f8f9fa; border-radius: 12px;
|
||||||
|
padding: 16px 18px; margin-bottom: 22px;
|
||||||
|
border-left: 4px solid #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-num { font-size: 0.8rem; color: #6c757d; font-family: monospace; }
|
||||||
|
|
||||||
|
.product-img {
|
||||||
|
width: 48px; height: 48px; border-radius: 8px;
|
||||||
|
object-fit: cover; border: 1px solid #e9ecef; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-placeholder {
|
||||||
|
width: 48px; height: 48px; border-radius: 8px;
|
||||||
|
background: #f0f0f0; display: flex; flex-shrink: 0;
|
||||||
|
align-items: center; justify-content: center; color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reason-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
|
||||||
|
.reason-item input[type="radio"] { display: none; }
|
||||||
|
|
||||||
|
.reason-label {
|
||||||
|
display: flex; align-items: center; gap: 14px;
|
||||||
|
padding: 13px 16px; border-radius: 10px;
|
||||||
|
border: 2px solid #e9ecef; cursor: pointer;
|
||||||
|
transition: all 0.18s; font-size: 0.92rem; background: white;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reason-label:hover { border-color: #dc3545; background: #fff8f8; }
|
||||||
|
|
||||||
|
.reason-item input[type="radio"]:checked + .reason-label {
|
||||||
|
border-color: #dc3545; background: #fff0f0;
|
||||||
|
color: #c0392b; font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-dot {
|
||||||
|
width: 20px; height: 20px; border-radius: 50%;
|
||||||
|
border: 2px solid #dee2e6; flex-shrink: 0;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
transition: all 0.18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reason-item input[type="radio"]:checked + .reason-label .radio-dot {
|
||||||
|
border-color: #dc3545; background: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reason-item input[type="radio"]:checked + .reason-label .radio-dot::after {
|
||||||
|
content: ''; width: 8px; height: 8px;
|
||||||
|
border-radius: 50%; background: white; display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box {
|
||||||
|
background: #fff3cd; border: 1px solid #ffc107;
|
||||||
|
border-radius: 10px; padding: 13px 16px;
|
||||||
|
color: #856404; font-size: 0.87rem; line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit-cancel {
|
||||||
|
width: 100%; padding: 13px;
|
||||||
|
background: #dc3545; color: white; border: none;
|
||||||
|
border-radius: 10px; font-weight: 700; font-size: 1rem;
|
||||||
|
transition: all 0.2s; cursor: pointer; letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit-cancel:hover {
|
||||||
|
background: #c82333;
|
||||||
|
box-shadow: 0 4px 14px rgba(220,53,69,0.35);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-kembali {
|
||||||
|
display: block; text-align: center; margin-top: 13px;
|
||||||
|
color: #6c757d; font-size: 0.88rem; text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-kembali:hover { color: #333; text-decoration: underline; }
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
20% { transform: translateX(-8px); }
|
||||||
|
40% { transform: translateX(8px); }
|
||||||
|
60% { transform: translateX(-5px); }
|
||||||
|
80% { transform: translateX(5px); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="page-hero">
|
||||||
|
<div class="container d-flex align-items-center gap-3">
|
||||||
|
<a href="{{ route('orders.detail', $transaksi->id) }}" class="btn btn-outline-light btn-sm">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
</a>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0"><i class="fas fa-times-circle me-2"></i>Batalkan Pesanan</h5>
|
||||||
|
<small class="font-monospace">{{ $transaksi->invoice_number }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cancel-wrap">
|
||||||
|
<div class="detail-card">
|
||||||
|
<div class="section-title">
|
||||||
|
<i class="fas fa-receipt me-2 text-danger"></i>Pesanan yang Akan Dibatalkan
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="order-summary-box">
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span class="invoice-num"><i class="fas fa-receipt me-1"></i>{{ $transaksi->invoice_number }}</span>
|
||||||
|
<small class="text-muted">{{ $transaksi->created_at->format('d M Y, H:i') }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach($transaksi->details->take(2) as $detail)
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-2">
|
||||||
|
@if($detail->product && $detail->product->image)
|
||||||
|
<img src="{{ asset('storage/' . $detail->product->image) }}" class="product-img" alt="">
|
||||||
|
@else
|
||||||
|
<div class="product-placeholder"><i class="fas fa-seedling"></i></div>
|
||||||
|
@endif
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-bold" style="font-size:0.88rem;">{{ $detail->product_name }}</div>
|
||||||
|
<small class="text-muted">{{ $detail->quantity }} × Rp{{ number_format($detail->price, 0, ',', '.') }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="fw-bold text-danger" style="font-size:0.88rem;">
|
||||||
|
Rp{{ number_format($detail->subtotal, 0, ',', '.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@if($transaksi->details->count() > 2)
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-plus me-1"></i>{{ $transaksi->details->count() - 2 }} produk lainnya
|
||||||
|
</small>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between border-top pt-2 mt-2">
|
||||||
|
<span class="fw-bold">Total Pembayaran</span>
|
||||||
|
<span class="fw-bold text-danger">Rp{{ number_format($transaksi->total_amount, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="warning-box mb-4">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
@if($transaksi->isAutoCancel())
|
||||||
|
<strong>Perhatian!</strong> Pesanan ini menggunakan <strong>COD</strong> dan belum diproses,
|
||||||
|
sehingga akan <strong>langsung dibatalkan</strong> tanpa perlu menunggu konfirmasi admin.
|
||||||
|
@else
|
||||||
|
<strong>Perhatian!</strong> Pengajuan pembatalan ini akan dikonfirmasi oleh admin terlebih dahulu.
|
||||||
|
Pesanan <strong>tidak langsung dibatalkan</strong> — kamu akan mendapat notifikasi setelah admin merespons.
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ route('orders.cancel.store', $transaksi->id) }}" method="POST" id="cancelForm">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="section-title">
|
||||||
|
<i class="fas fa-question-circle me-2 text-danger"></i>Pilih Alasan Pembatalan
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@error('reason')
|
||||||
|
<div class="alert alert-danger py-2 mb-3" style="border-radius:8px; font-size:0.87rem;">
|
||||||
|
<i class="fas fa-exclamation-circle me-1"></i>{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
|
||||||
|
<div class="reason-list mb-4">
|
||||||
|
@foreach($reasons as $value => $label)
|
||||||
|
<div class="reason-item">
|
||||||
|
<input type="radio" name="reason"
|
||||||
|
id="reason_{{ $loop->index }}"
|
||||||
|
value="{{ $value }}"
|
||||||
|
{{ old('reason') == $value ? 'checked' : '' }}>
|
||||||
|
<label class="reason-label" for="reason_{{ $loop->index }}">
|
||||||
|
<span class="radio-dot"></span>
|
||||||
|
{{ $label }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold" style="font-size:0.92rem;">
|
||||||
|
Keterangan Tambahan
|
||||||
|
<span class="text-muted fw-normal">(opsional)</span>
|
||||||
|
</label>
|
||||||
|
<textarea name="description"
|
||||||
|
class="form-control @error('description') is-invalid @enderror"
|
||||||
|
rows="3" maxlength="500"
|
||||||
|
placeholder="Ceritakan lebih detail alasanmu (opsional)...">{{ old('description') }}</textarea>
|
||||||
|
@error('description')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
<small class="text-muted">Maks. 500 karakter</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn-submit-cancel" id="btnSubmitCancel">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>Kirim Pengajuan Pembatalan
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="{{ route('orders.detail', $transaksi->id) }}" class="btn-kembali">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>Batal, kembali ke detail pesanan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Modal Konfirmasi --}}
|
||||||
|
<div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" style="max-width:400px;">
|
||||||
|
<div class="modal-content border-0" style="border-radius:16px; overflow:hidden;">
|
||||||
|
<div class="modal-body text-center p-4">
|
||||||
|
<div class="mb-3" style="font-size:2.8rem;">😔</div>
|
||||||
|
<h5 class="fw-bold mb-2">Yakin ingin membatalkan?</h5>
|
||||||
|
<p class="text-muted mb-4" style="font-size:0.88rem; line-height:1.5;">
|
||||||
|
@if($transaksi->isAutoCancel())
|
||||||
|
Pesanan COD ini akan <strong>langsung dibatalkan</strong> sekarang juga.
|
||||||
|
@else
|
||||||
|
Pengajuan akan dikirim ke admin untuk ditinjau.
|
||||||
|
Pesanan tidak langsung dibatalkan sampai admin menyetujui.
|
||||||
|
@endif
|
||||||
|
</p>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary flex-fill"
|
||||||
|
data-bs-dismiss="modal">
|
||||||
|
Tidak Jadi
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-danger flex-fill"
|
||||||
|
id="btnConfirmSubmit">
|
||||||
|
Ya, Ajukan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Script langsung di dalam section content --}}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
||||||
|
document.getElementById('btnSubmitCancel').addEventListener('click', function () {
|
||||||
|
const selected = document.querySelector('input[name="reason"]:checked');
|
||||||
|
if (!selected) {
|
||||||
|
const list = document.querySelector('.reason-list');
|
||||||
|
list.style.animation = 'none';
|
||||||
|
list.offsetHeight;
|
||||||
|
list.style.animation = 'shake 0.4s ease';
|
||||||
|
alert('Pilih salah satu alasan pembatalan terlebih dahulu.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('confirmModal'));
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnConfirmSubmit').addEventListener('click', function () {
|
||||||
|
document.getElementById('cancelForm').submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Profil Saya - Bibit Cabai')
|
||||||
|
|
||||||
|
@section('styles')
|
||||||
|
<style>
|
||||||
|
body { background: #f8f9fa; font-family: 'Segoe UI', sans-serif; }
|
||||||
|
|
||||||
|
.profile-hero {
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
padding: 40px 0 80px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-circle {
|
||||||
|
width: 90px; height: 90px; border-radius: 50%;
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 2.5rem; font-weight: bold; color: white;
|
||||||
|
border: 4px solid rgba(255,255,255,0.5);
|
||||||
|
margin: 0 auto 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
|
||||||
|
margin-top: -50px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-profile .nav-link {
|
||||||
|
color: #6c757d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-profile .nav-link.active,
|
||||||
|
.nav-profile .nav-link:hover {
|
||||||
|
background: #e8f5e9;
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-profile .nav-link i { width: 20px; }
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #28a745;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(40,167,69,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-green {
|
||||||
|
background: linear-gradient(45deg, #28a745, #20c997);
|
||||||
|
border: none; color: white; font-weight: 600;
|
||||||
|
padding: 10px 25px; border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-green:hover { opacity: 0.9; color: white; }
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.1rem; font-weight: 700;
|
||||||
|
color: #333; margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-badge {
|
||||||
|
background: #f0fff4; border: 1px solid #b7ebc8;
|
||||||
|
border-radius: 8px; padding: 10px 15px;
|
||||||
|
font-size: 0.85rem; color: #2d6a4f;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
{{-- Hero --}}
|
||||||
|
<div class="profile-hero text-center">
|
||||||
|
<div class="avatar-circle">{{ strtoupper(substr(auth()->user()->name, 0, 1)) }}</div>
|
||||||
|
<h4 class="mb-1">{{ auth()->user()->name }}</h4>
|
||||||
|
<small>{{ auth()->user()->email }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
|
||||||
|
{{-- Sidebar Menu --}}
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="profile-card p-0 overflow-hidden">
|
||||||
|
<div class="p-3 border-bottom">
|
||||||
|
<small class="text-muted text-uppercase fw-bold" style="font-size:0.7rem;">Menu</small>
|
||||||
|
</div>
|
||||||
|
<nav class="nav flex-column nav-profile p-2">
|
||||||
|
<a href="{{ route('profile') }}" class="nav-link active">
|
||||||
|
<i class="fas fa-user me-2"></i>Profil Saya
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('orders.my') }}" class="nav-link">
|
||||||
|
<i class="fas fa-shopping-bag me-2"></i>Pesanan Saya
|
||||||
|
</a>
|
||||||
|
<hr class="my-2">
|
||||||
|
<a href="{{ route('home') }}" class="nav-link text-muted">
|
||||||
|
<i class="fas fa-home me-2"></i>Kembali ke Beranda
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Konten --}}
|
||||||
|
<div class="col-md-9">
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Form Edit Profil --}}
|
||||||
|
<div class="profile-card mb-4">
|
||||||
|
<div class="section-title"><i class="fas fa-user-edit me-2 text-success"></i>Edit Profil</div>
|
||||||
|
|
||||||
|
<div class="info-badge">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Email tidak dapat diubah melalui halaman ini. Email harus menggunakan <strong>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('profile.update') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Nama Lengkap</label>
|
||||||
|
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror"
|
||||||
|
value="{{ old('name', auth()->user()->name) }}" required>
|
||||||
|
@error('name')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Email</label>
|
||||||
|
<input type="email" class="form-control bg-light" value="{{ auth()->user()->email }}" disabled>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Nomor Telepon</label>
|
||||||
|
<input type="text" name="phone" class="form-control @error('phone') is-invalid @enderror"
|
||||||
|
value="{{ old('phone', auth()->user()->phone ?? '+62') }}"
|
||||||
|
placeholder="+62812xxxxxxxx">
|
||||||
|
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-bold">Alamat</label>
|
||||||
|
<textarea name="address" rows="4"
|
||||||
|
class="form-control @error('address') is-invalid @enderror"
|
||||||
|
style="min-height:100px; resize:vertical;"
|
||||||
|
placeholder="Contoh: Jl. Mawar No. 10, RT 02/RW 03, Kelurahan Sumbersari, Jember">{{ old('address', auth()->user()->address) }}</textarea>
|
||||||
|
@error('address')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-green">
|
||||||
|
<i class="fas fa-save me-2"></i>Simpan Perubahan
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Form Ganti Password --}}
|
||||||
|
<div class="profile-card">
|
||||||
|
<div class="section-title"><i class="fas fa-lock me-2 text-success"></i>Ganti Password</div>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('profile.password') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Password Lama</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" name="current_password" id="cur_pass"
|
||||||
|
class="form-control @error('current_password') is-invalid @enderror"
|
||||||
|
placeholder="Masukkan password lama" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('cur_pass','eye0')">
|
||||||
|
<i class="fas fa-eye" id="eye0"></i>
|
||||||
|
</button>
|
||||||
|
@error('current_password')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Password Baru</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" name="password" id="new_pass"
|
||||||
|
class="form-control @error('password') is-invalid @enderror"
|
||||||
|
placeholder="Minimal 8 karakter" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('new_pass','eye1')">
|
||||||
|
<i class="fas fa-eye" id="eye1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@error('password')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Konfirmasi Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" name="password_confirmation" id="conf_pass"
|
||||||
|
class="form-control" placeholder="Ulangi password baru" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('conf_pass','eye2')">
|
||||||
|
<i class="fas fa-eye" id="eye2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-green">
|
||||||
|
<i class="fas fa-key me-2"></i>Ganti Password
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('scripts')
|
||||||
|
<script>
|
||||||
|
function togglePassword(fieldId, iconId) {
|
||||||
|
const field = document.getElementById(fieldId);
|
||||||
|
const icon = document.getElementById(iconId);
|
||||||
|
if (field.type === 'password') {
|
||||||
|
field.type = 'text';
|
||||||
|
icon.classList.replace('fa-eye', 'fa-eye-slash');
|
||||||
|
} else {
|
||||||
|
field.type = 'password';
|
||||||
|
icon.classList.replace('fa-eye-slash', 'fa-eye');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|
@ -0,0 +1,444 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Detail Pesanan - Bibit Cabai')
|
||||||
|
|
||||||
|
@section('styles')
|
||||||
|
<style>
|
||||||
|
body { background: #f8f9fa; font-family: 'Segoe UI', sans-serif; }
|
||||||
|
|
||||||
|
.page-hero {
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
padding: 35px 0; color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card {
|
||||||
|
background: white; border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.07);
|
||||||
|
padding: 25px; margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1rem; font-weight: 700; color: #333;
|
||||||
|
margin-bottom: 18px; padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status {
|
||||||
|
padding: 6px 16px; border-radius: 20px;
|
||||||
|
font-size: 0.85rem; font-weight: 600; display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pending { background: #fff3cd; color: #856404; }
|
||||||
|
.status-processing { background: #cce5ff; color: #004085; }
|
||||||
|
.status-shipped { background: #d1ecf1; color: #0c5460; }
|
||||||
|
.status-delivered { background: #d4edda; color: #155724; }
|
||||||
|
.status-cancelled { background: #f8d7da; color: #721c24; }
|
||||||
|
.payment-paid { background: #d4edda; color: #155724; }
|
||||||
|
.payment-pending { background: #fff3cd; color: #856404; }
|
||||||
|
|
||||||
|
.info-row { display: flex; padding: 10px 0; border-bottom: 1px solid #f8f9fa; }
|
||||||
|
.info-row:last-child { border-bottom: none; }
|
||||||
|
.info-label { width: 160px; color: #6c757d; font-size: 0.9rem; flex-shrink: 0; }
|
||||||
|
.info-value { flex: 1; font-weight: 500; color: #333; }
|
||||||
|
|
||||||
|
.product-img {
|
||||||
|
width: 60px; height: 60px; border-radius: 10px;
|
||||||
|
object-fit: cover; border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-placeholder {
|
||||||
|
width: 60px; height: 60px; border-radius: 10px;
|
||||||
|
background: #f0f0f0; display: flex;
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
color: #aaa; font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline { position: relative; padding-left: 25px; }
|
||||||
|
.timeline::before {
|
||||||
|
content: ''; position: absolute; left: 8px; top: 0; bottom: 0;
|
||||||
|
width: 2px; background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item { position: relative; margin-bottom: 20px; }
|
||||||
|
.timeline-item::before {
|
||||||
|
content: ''; position: absolute; left: -21px; top: 4px;
|
||||||
|
width: 12px; height: 12px; border-radius: 50%;
|
||||||
|
background: #dee2e6; border: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item.done::before { background: #28a745; }
|
||||||
|
.timeline-item.cancelled::before { background: #dc3545; }
|
||||||
|
|
||||||
|
.total-row {
|
||||||
|
display: flex; justify-content: space-between;
|
||||||
|
padding: 8px 0; font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-row.grand {
|
||||||
|
font-size: 1.1rem; font-weight: 700;
|
||||||
|
color: #28a745; border-top: 2px solid #f0f0f0;
|
||||||
|
padding-top: 12px; margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Card pembatalan ── */
|
||||||
|
.cancel-action-card {
|
||||||
|
border-radius: 14px; padding: 20px;
|
||||||
|
border: 1.5px dashed #f5c6cb;
|
||||||
|
background: #fff5f5; margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-status-card {
|
||||||
|
border-radius: 14px; padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-status-card.pending { background: #fffbf0; border: 1.5px solid #ffc107; }
|
||||||
|
.cancel-status-card.approved { background: #fff5f5; border: 1.5px solid #dc3545; }
|
||||||
|
.cancel-status-card.rejected { background: #f0fff4; border: 1.5px solid #28a745; }
|
||||||
|
|
||||||
|
.cancel-status-badge {
|
||||||
|
display: inline-flex; align-items: center; gap: 6px;
|
||||||
|
padding: 5px 14px; border-radius: 20px;
|
||||||
|
font-size: 0.78rem; font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-status-badge.pending { background: #fff3cd; color: #856404; }
|
||||||
|
.cancel-status-badge.approved { background: #f8d7da; color: #721c24; }
|
||||||
|
.cancel-status-badge.rejected { background: #d4edda; color: #155724; }
|
||||||
|
|
||||||
|
.cancel-detail-row {
|
||||||
|
display: flex; gap: 10px; font-size: 0.87rem;
|
||||||
|
padding: 6px 0; border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-detail-row:last-child { border-bottom: none; }
|
||||||
|
.cancel-detail-label { width: 120px; color: #6c757d; flex-shrink: 0; }
|
||||||
|
.cancel-detail-value { flex: 1; color: #333; font-weight: 500; }
|
||||||
|
|
||||||
|
.admin-note-box {
|
||||||
|
border-radius: 8px; padding: 10px 14px;
|
||||||
|
font-size: 0.85rem; margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-note-box.approved { background: #fff0f0; border-left: 3px solid #dc3545; }
|
||||||
|
.admin-note-box.rejected { background: #f0fff4; border-left: 3px solid #28a745; }
|
||||||
|
|
||||||
|
/* Tombol Batalkan */
|
||||||
|
.btn-batalkan {
|
||||||
|
display: inline-flex; align-items: center; gap: 8px;
|
||||||
|
background: #dc3545; color: white;
|
||||||
|
border: none; padding: 10px 22px; border-radius: 10px;
|
||||||
|
font-weight: 600; font-size: 0.9rem;
|
||||||
|
transition: all 0.2s; text-decoration: none; cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-batalkan:hover {
|
||||||
|
background: #c82333; color: white;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(220,53,69,0.3);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="page-hero">
|
||||||
|
<div class="container d-flex align-items-center gap-3">
|
||||||
|
<a href="{{ route('orders.my') }}" class="btn btn-outline-light btn-sm">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
</a>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">Detail Pesanan</h5>
|
||||||
|
<small class="font-monospace">{{ $order->invoice_number }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
|
||||||
|
{{-- Flash Message --}}
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show mb-4" role="alert" style="border-radius:12px;">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert" style="border-radius:12px;">
|
||||||
|
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
|
||||||
|
{{-- Kolom Kiri --}}
|
||||||
|
<div class="col-md-8">
|
||||||
|
|
||||||
|
{{-- Status Pesanan --}}
|
||||||
|
<div class="detail-card">
|
||||||
|
<div class="section-title"><i class="fas fa-info-circle me-2 text-success"></i>Status Pesanan</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-3 mb-4 flex-wrap">
|
||||||
|
<div>
|
||||||
|
<small class="text-muted d-block">Status Pesanan</small>
|
||||||
|
<span class="order-status status-{{ $order->order_status }}">
|
||||||
|
@switch($order->order_status)
|
||||||
|
@case('pending') <i class="fas fa-clock me-1"></i>Menunggu Konfirmasi @break
|
||||||
|
@case('processing') <i class="fas fa-cog me-1"></i>Sedang Diproses @break
|
||||||
|
@case('shipped') <i class="fas fa-truck me-1"></i>Sedang Dikirim @break
|
||||||
|
@case('delivered') <i class="fas fa-check-circle me-1"></i>Pesanan Selesai @break
|
||||||
|
@case('cancelled') <i class="fas fa-times-circle me-1"></i>Dibatalkan @break
|
||||||
|
@endswitch
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small class="text-muted d-block">Status Pembayaran</small>
|
||||||
|
<span class="order-status {{ $order->payment_status === 'paid' ? 'payment-paid' : 'payment-pending' }}">
|
||||||
|
@if($order->payment_status === 'paid')
|
||||||
|
<i class="fas fa-check me-1"></i>Lunas
|
||||||
|
@else
|
||||||
|
<i class="fas fa-hourglass me-1"></i>Belum Dibayar
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@php
|
||||||
|
$statuses = ['pending', 'processing', 'shipped', 'delivered'];
|
||||||
|
$currentIdx = array_search($order->order_status, $statuses);
|
||||||
|
$isCancelled = $order->order_status === 'cancelled';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="timeline">
|
||||||
|
@if($isCancelled)
|
||||||
|
<div class="timeline-item done">
|
||||||
|
<strong>Pesanan Diterima</strong>
|
||||||
|
<div class="text-muted" style="font-size:0.82rem;">{{ $order->created_at->format('d M Y, H:i') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item cancelled">
|
||||||
|
<strong class="text-danger">Pesanan Dibatalkan</strong>
|
||||||
|
@if($order->cancellation && $order->cancellation->reviewed_at)
|
||||||
|
<div class="text-muted" style="font-size:0.82rem;">{{ $order->cancellation->reviewed_at->format('d M Y, H:i') }}</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="timeline-item {{ $currentIdx >= 0 ? 'done' : '' }}">
|
||||||
|
<strong>Pesanan Diterima</strong>
|
||||||
|
<div class="text-muted" style="font-size:0.82rem;">{{ $order->created_at->format('d M Y, H:i') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item {{ $currentIdx >= 1 ? 'done' : '' }}">
|
||||||
|
<strong>Sedang Diproses</strong>
|
||||||
|
<div class="text-muted" style="font-size:0.82rem;">Admin sedang mempersiapkan pesanan</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item {{ $currentIdx >= 2 ? 'done' : '' }}">
|
||||||
|
<strong>Dikirim</strong>
|
||||||
|
<div class="text-muted" style="font-size:0.82rem;">Pesanan dalam perjalanan</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item {{ $currentIdx >= 3 ? 'done' : '' }}">
|
||||||
|
<strong>Selesai</strong>
|
||||||
|
<div class="text-muted" style="font-size:0.82rem;">Pesanan telah diterima</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ── TOMBOL BATALKAN / STATUS PENGAJUAN ── --}}
|
||||||
|
@php
|
||||||
|
$isCancellable = in_array($order->order_status, ['pending','processing'])
|
||||||
|
&& !$order->cancellation;
|
||||||
|
$cancellation = $order->cancellation;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
{{-- A) Bisa dibatalkan → tampilkan tombol --}}
|
||||||
|
@if($isCancellable)
|
||||||
|
<div class="cancel-action-card">
|
||||||
|
<div class="d-flex align-items-start gap-3">
|
||||||
|
<div style="font-size:1.6rem; line-height:1;">⚠️</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-bold mb-1" style="font-size:0.95rem;">Ingin membatalkan pesanan ini?</div>
|
||||||
|
<p class="text-muted mb-3" style="font-size:0.85rem; line-height:1.5;">
|
||||||
|
Pengajuan pembatalan akan dikirim ke admin untuk ditinjau terlebih dahulu.
|
||||||
|
Pesanan <strong>tidak langsung dibatalkan</strong> sampai admin menyetujui.
|
||||||
|
</p>
|
||||||
|
<a href="{{ route('orders.cancel', $order->id) }}" class="btn-batalkan">
|
||||||
|
<i class="fas fa-times-circle"></i>Batalkan Pesanan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- B) Sudah ada pengajuan → tampilkan status --}}
|
||||||
|
@if($cancellation)
|
||||||
|
<div class="cancel-status-card {{ $cancellation->status }}">
|
||||||
|
<div class="cancel-status-badge {{ $cancellation->status }}">
|
||||||
|
@if($cancellation->status === 'pending')
|
||||||
|
<i class="fas fa-clock"></i> Pengajuan Pembatalan — Menunggu Konfirmasi Admin
|
||||||
|
@elseif($cancellation->status === 'approved')
|
||||||
|
<i class="fas fa-check-circle"></i> Pembatalan Disetujui
|
||||||
|
@else
|
||||||
|
<i class="fas fa-times-circle"></i> Pengajuan Pembatalan Ditolak
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cancel-detail-row">
|
||||||
|
<span class="cancel-detail-label">Alasan</span>
|
||||||
|
<span class="cancel-detail-value">{{ $cancellation->reason }}</span>
|
||||||
|
</div>
|
||||||
|
@if($cancellation->description)
|
||||||
|
<div class="cancel-detail-row">
|
||||||
|
<span class="cancel-detail-label">Keterangan</span>
|
||||||
|
<span class="cancel-detail-value">{{ $cancellation->description }}</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="cancel-detail-row">
|
||||||
|
<span class="cancel-detail-label">Diajukan</span>
|
||||||
|
<span class="cancel-detail-value">{{ $cancellation->created_at->format('d M Y, H:i') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($cancellation->status === 'pending')
|
||||||
|
<p class="text-muted mt-2 mb-0" style="font-size:0.82rem;">
|
||||||
|
<i class="fas fa-info-circle me-1"></i>
|
||||||
|
Admin akan segera meninjau permintaanmu. Harap tunggu konfirmasi.
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($cancellation->admin_note)
|
||||||
|
<div class="admin-note-box {{ $cancellation->status }}">
|
||||||
|
<small class="text-muted d-block mb-1 fw-bold">Catatan dari Admin:</small>
|
||||||
|
{{ $cancellation->admin_note }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($cancellation->status === 'rejected')
|
||||||
|
<p class="text-muted mt-2 mb-0" style="font-size:0.82rem;">
|
||||||
|
<i class="fas fa-info-circle me-1"></i>
|
||||||
|
Pesanan tetap berjalan seperti biasa. Silakan tunggu pengiriman pesananmu.
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Produk Dipesan --}}
|
||||||
|
<div class="detail-card">
|
||||||
|
<div class="section-title"><i class="fas fa-seedling me-2 text-success"></i>Produk Dipesan</div>
|
||||||
|
|
||||||
|
@foreach($order->details as $detail)
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-3 pb-3 border-bottom">
|
||||||
|
@if($detail->product && $detail->product->image)
|
||||||
|
<img src="{{ asset('storage/' . $detail->product->image) }}"
|
||||||
|
class="product-img" alt="{{ $detail->product_name }}">
|
||||||
|
@else
|
||||||
|
<div class="product-img-placeholder">
|
||||||
|
<i class="fas fa-seedling"></i>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-bold">{{ $detail->product_name }}</div>
|
||||||
|
<small class="text-muted">{{ $detail->quantity }} x Rp{{ number_format($detail->price, 0, ',', '.') }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-end fw-bold">
|
||||||
|
Rp{{ number_format($detail->subtotal, 0, ',', '.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="text-muted">Subtotal</span>
|
||||||
|
<span>Rp{{ number_format($order->subtotal, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="text-muted">Ongkos Kirim</span>
|
||||||
|
<span>Rp{{ number_format($order->shipping_cost, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-row grand">
|
||||||
|
<span>Total Pembayaran</span>
|
||||||
|
<span>Rp{{ number_format($order->total_amount, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Kolom Kanan --}}
|
||||||
|
<div class="col-md-4">
|
||||||
|
|
||||||
|
<div class="detail-card">
|
||||||
|
<div class="section-title"><i class="fas fa-map-marker-alt me-2 text-success"></i>Alamat Pengiriman</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Nama</div>
|
||||||
|
<div class="info-value">{{ $order->customer_name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Telepon</div>
|
||||||
|
<div class="info-value">{{ $order->customer_phone }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Alamat</div>
|
||||||
|
<div class="info-value">{{ $order->shipping_address }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Kecamatan</div>
|
||||||
|
<div class="info-value">{{ $order->city }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Provinsi</div>
|
||||||
|
<div class="info-value">{{ $order->province }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Kode Pos</div>
|
||||||
|
<div class="info-value">{{ $order->postal_code }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-card">
|
||||||
|
<div class="section-title"><i class="fas fa-credit-card me-2 text-success"></i>Info Pembayaran</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Metode</div>
|
||||||
|
<div class="info-value">
|
||||||
|
@switch($order->payment_method)
|
||||||
|
@case('transfer') Transfer Bank @break
|
||||||
|
@case('bri') Transfer BRI @break
|
||||||
|
@case('qris') QRIS @break
|
||||||
|
@case('cod') COD (Bayar di Tempat) @break
|
||||||
|
@default {{ $order->payment_method }}
|
||||||
|
@endswitch
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Status</div>
|
||||||
|
<div class="info-value">
|
||||||
|
@if($order->payment_status === 'paid')
|
||||||
|
<span class="text-success fw-bold"><i class="fas fa-check me-1"></i>Lunas</span>
|
||||||
|
@else
|
||||||
|
<span class="text-warning fw-bold"><i class="fas fa-hourglass me-1"></i>Belum Dibayar</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if($order->paid_at)
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Dibayar</div>
|
||||||
|
<div class="info-value">{{ $order->paid_at->format('d M Y, H:i') }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if($order->notes)
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Catatan</div>
|
||||||
|
<div class="info-value">{{ $order->notes }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ route('orders.my') }}" class="btn btn-outline-success w-100">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Kembali ke Pesanan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
@ -0,0 +1,335 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Pesanan Saya - Bibit Cabai')
|
||||||
|
|
||||||
|
@section('styles')
|
||||||
|
<style>
|
||||||
|
body { background: #f8f9fa; font-family: 'Segoe UI', sans-serif; }
|
||||||
|
|
||||||
|
.page-hero {
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
padding: 35px 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-profile .nav-link {
|
||||||
|
color: #6c757d; border-radius: 8px;
|
||||||
|
padding: 10px 16px; font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-profile .nav-link.active,
|
||||||
|
.nav-profile .nav-link:hover {
|
||||||
|
background: #e8f5e9; color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-profile .nav-link i { width: 20px; }
|
||||||
|
|
||||||
|
.profile-card {
|
||||||
|
background: white; border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.07);
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tabs .nav-link {
|
||||||
|
color: #6c757d; border-radius: 20px;
|
||||||
|
padding: 6px 16px; font-size: 0.85rem; font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tabs .nav-link.active { background: #28a745; color: white; }
|
||||||
|
.filter-tabs .nav-link:hover:not(.active) { background: #f0f0f0; }
|
||||||
|
|
||||||
|
.order-card {
|
||||||
|
border: 1px solid #e9ecef; border-radius: 12px;
|
||||||
|
padding: 20px; margin-bottom: 16px;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||||
|
|
||||||
|
/* ── Pesanan pending/processing punya border merah tipis kalau bisa dibatalkan ── */
|
||||||
|
.order-card.cancellable {
|
||||||
|
border-color: #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status {
|
||||||
|
padding: 4px 12px; border-radius: 20px;
|
||||||
|
font-size: 0.75rem; font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pending { background: #fff3cd; color: #856404; }
|
||||||
|
.status-processing { background: #cce5ff; color: #004085; }
|
||||||
|
.status-shipped { background: #d1ecf1; color: #0c5460; }
|
||||||
|
.status-delivered { background: #d4edda; color: #155724; }
|
||||||
|
.status-cancelled { background: #f8d7da; color: #721c24; }
|
||||||
|
|
||||||
|
.payment-paid { background: #d4edda; color: #155724; }
|
||||||
|
.payment-pending { background: #fff3cd; color: #856404; }
|
||||||
|
|
||||||
|
/* Badge pengajuan pembatalan sedang pending */
|
||||||
|
.cancel-badge {
|
||||||
|
background: #fff3cd; color: #856404;
|
||||||
|
border: 1px dashed #ffc107;
|
||||||
|
padding: 3px 10px; border-radius: 20px;
|
||||||
|
font-size: 0.72rem; font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img {
|
||||||
|
width: 55px; height: 55px; border-radius: 8px;
|
||||||
|
object-fit: cover; border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-placeholder {
|
||||||
|
width: 55px; height: 55px; border-radius: 8px;
|
||||||
|
background: #f0f0f0; display: flex;
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
color: #aaa; font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-mini {
|
||||||
|
text-align: center; padding: 15px;
|
||||||
|
border-radius: 10px; background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-mini .num { font-size: 1.5rem; font-weight: 700; color: #28a745; }
|
||||||
|
.stat-mini .lbl { font-size: 0.75rem; color: #6c757d; }
|
||||||
|
|
||||||
|
.invoice-num { font-size: 0.8rem; color: #6c757d; font-family: monospace; }
|
||||||
|
|
||||||
|
/* Tombol batalkan */
|
||||||
|
.btn-cancel-order {
|
||||||
|
font-size: 0.8rem; padding: 5px 14px;
|
||||||
|
border-radius: 20px; border: 1.5px solid #dc3545;
|
||||||
|
color: #dc3545; background: transparent;
|
||||||
|
font-weight: 600; transition: all 0.2s; text-decoration: none;
|
||||||
|
display: inline-flex; align-items: center; gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel-order:hover {
|
||||||
|
background: #dc3545; color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="page-hero">
|
||||||
|
<div class="container">
|
||||||
|
<h4 class="mb-0"><i class="fas fa-shopping-bag me-2"></i>Pesanan Saya</h4>
|
||||||
|
<small>Pantau status semua pesanan kamu di sini</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
|
||||||
|
{{-- Sidebar --}}
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="profile-card p-0 overflow-hidden mb-3">
|
||||||
|
<div class="p-3 border-bottom">
|
||||||
|
<small class="text-muted text-uppercase fw-bold" style="font-size:0.7rem;">Menu</small>
|
||||||
|
</div>
|
||||||
|
<nav class="nav flex-column nav-profile p-2">
|
||||||
|
<a href="{{ route('profile') }}" class="nav-link">
|
||||||
|
<i class="fas fa-user me-2"></i>Profil Saya
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('orders.my') }}" class="nav-link active">
|
||||||
|
<i class="fas fa-shopping-bag me-2"></i>Pesanan Saya
|
||||||
|
</a>
|
||||||
|
<hr class="my-2">
|
||||||
|
<a href="{{ route('home') }}" class="nav-link text-muted">
|
||||||
|
<i class="fas fa-home me-2"></i>Kembali ke Beranda
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Statistik Mini --}}
|
||||||
|
<div class="profile-card">
|
||||||
|
<small class="text-muted text-uppercase fw-bold d-block mb-3" style="font-size:0.7rem;">Ringkasan</small>
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="stat-mini">
|
||||||
|
<div class="num">{{ $totalOrders }}</div>
|
||||||
|
<div class="lbl">Total</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="stat-mini">
|
||||||
|
<div class="num">{{ $pendingOrders }}</div>
|
||||||
|
<div class="lbl">Menunggu</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="stat-mini">
|
||||||
|
<div class="num">{{ $shippedOrders }}</div>
|
||||||
|
<div class="lbl">Dikirim</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="stat-mini">
|
||||||
|
<div class="num">{{ $doneOrders }}</div>
|
||||||
|
<div class="lbl">Selesai</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Konten Pesanan --}}
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="profile-card">
|
||||||
|
|
||||||
|
{{-- Flash Message --}}
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show mb-4" role="alert" style="border-radius:10px;">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Filter Tabs --}}
|
||||||
|
<div class="d-flex flex-wrap gap-2 mb-4">
|
||||||
|
<nav class="nav filter-tabs flex-wrap gap-1">
|
||||||
|
<a href="{{ route('orders.my') }}"
|
||||||
|
class="nav-link {{ $status === 'all' ? 'active' : '' }}">
|
||||||
|
Semua ({{ $totalOrders }})
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('orders.my', ['status' => 'pending']) }}"
|
||||||
|
class="nav-link {{ $status === 'pending' ? 'active' : '' }}">
|
||||||
|
Menunggu ({{ $pendingOrders }})
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('orders.my', ['status' => 'processing']) }}"
|
||||||
|
class="nav-link {{ $status === 'processing' ? 'active' : '' }}">
|
||||||
|
Diproses ({{ $processOrders }})
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('orders.my', ['status' => 'shipped']) }}"
|
||||||
|
class="nav-link {{ $status === 'shipped' ? 'active' : '' }}">
|
||||||
|
Dikirim ({{ $shippedOrders }})
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('orders.my', ['status' => 'delivered']) }}"
|
||||||
|
class="nav-link {{ $status === 'delivered' ? 'active' : '' }}">
|
||||||
|
Selesai ({{ $doneOrders }})
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('orders.my', ['status' => 'cancelled']) }}"
|
||||||
|
class="nav-link {{ $status === 'cancelled' ? 'active' : '' }}">
|
||||||
|
Dibatalkan ({{ $cancelOrders }})
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Daftar Pesanan --}}
|
||||||
|
@forelse($orders as $order)
|
||||||
|
|
||||||
|
@php
|
||||||
|
$isCancellable = $order->canBeCancelled();
|
||||||
|
$hasPending = $order->cancellation && $order->cancellation->status === 'pending';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="order-card {{ $isCancellable ? 'cancellable' : '' }}">
|
||||||
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||||
|
<div>
|
||||||
|
<div class="invoice-num mb-1">
|
||||||
|
<i class="fas fa-receipt me-1"></i>{{ $order->invoice_number }}
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">{{ $order->created_at->format('d M Y, H:i') }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2 flex-wrap justify-content-end align-items-center">
|
||||||
|
{{-- Badge pengajuan sedang menunggu --}}
|
||||||
|
@if($hasPending)
|
||||||
|
<span class="cancel-badge">
|
||||||
|
<i class="fas fa-clock me-1"></i>Menunggu Konfirmasi Batal
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<span class="order-status status-{{ $order->order_status }}">
|
||||||
|
@switch($order->order_status)
|
||||||
|
@case('pending') <i class="fas fa-clock me-1"></i>Menunggu @break
|
||||||
|
@case('processing') <i class="fas fa-cog me-1"></i>Diproses @break
|
||||||
|
@case('shipped') <i class="fas fa-truck me-1"></i>Dikirim @break
|
||||||
|
@case('delivered') <i class="fas fa-check-circle me-1"></i>Selesai @break
|
||||||
|
@case('cancelled') <i class="fas fa-times-circle me-1"></i>Dibatalkan @break
|
||||||
|
@endswitch
|
||||||
|
</span>
|
||||||
|
<span class="order-status {{ $order->payment_status === 'paid' ? 'payment-paid' : 'payment-pending' }}">
|
||||||
|
@if($order->payment_status === 'paid')
|
||||||
|
<i class="fas fa-check me-1"></i>Lunas
|
||||||
|
@else
|
||||||
|
<i class="fas fa-hourglass me-1"></i>Belum Bayar
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
@foreach($order->details->take(2) as $detail)
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-2">
|
||||||
|
@if($detail->product && $detail->product->image)
|
||||||
|
<img src="{{ asset('storage/' . $detail->product->image) }}"
|
||||||
|
class="product-img" alt="{{ $detail->product_name }}">
|
||||||
|
@else
|
||||||
|
<div class="product-img-placeholder">
|
||||||
|
<i class="fas fa-seedling"></i>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-bold" style="font-size:0.9rem;">{{ $detail->product_name }}</div>
|
||||||
|
<small class="text-muted">{{ $detail->quantity }} x Rp{{ number_format($detail->price, 0, ',', '.') }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<small class="fw-bold">Rp{{ number_format($detail->subtotal, 0, ',', '.') }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@if($order->details->count() > 2)
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-plus me-1"></i>{{ $order->details->count() - 2 }} produk lainnya
|
||||||
|
</small>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center pt-3 border-top">
|
||||||
|
<div>
|
||||||
|
<small class="text-muted">Total Pembayaran</small>
|
||||||
|
<div class="fw-bold text-success" style="font-size:1.1rem;">
|
||||||
|
Rp{{ number_format($order->total_amount, 0, ',', '.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2 align-items-center">
|
||||||
|
{{-- Tombol Batalkan Pesanan --}}
|
||||||
|
@if($isCancellable)
|
||||||
|
<a href="{{ route('orders.cancel', $order->id) }}" class="btn-cancel-order">
|
||||||
|
<i class="fas fa-times-circle"></i>Batalkan
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<a href="{{ route('orders.detail', $order->id) }}"
|
||||||
|
class="btn btn-outline-success btn-sm">
|
||||||
|
<i class="fas fa-eye me-1"></i>Lihat Detail
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@empty
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-shopping-bag fa-3x text-muted mb-3 d-block"></i>
|
||||||
|
<h6 class="text-muted">Belum ada pesanan</h6>
|
||||||
|
<a href="{{ route('home') }}" class="btn btn-success mt-2">
|
||||||
|
<i class="fas fa-seedling me-2"></i>Mulai Belanja
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
|
||||||
|
@if($orders->hasPages())
|
||||||
|
<div class="d-flex justify-content-center mt-4">
|
||||||
|
{{ $orders->appends(['status' => $status])->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
||||||
100
routes/web.php
100
routes/web.php
|
|
@ -11,6 +11,9 @@
|
||||||
use App\Http\Controllers\CheckoutController;
|
use App\Http\Controllers\CheckoutController;
|
||||||
use App\Http\Controllers\TransaksiController;
|
use App\Http\Controllers\TransaksiController;
|
||||||
use App\Http\Controllers\UserController;
|
use App\Http\Controllers\UserController;
|
||||||
|
use App\Http\Controllers\ProfileController;
|
||||||
|
use App\Http\Controllers\Auth\ForgotPasswordController;
|
||||||
|
use App\Http\Controllers\AdminForgotPasswordController; // ← tambahkan ini
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -21,10 +24,12 @@
|
||||||
// Homepage
|
// Homepage
|
||||||
Route::get('/', [HomeController::class, 'index'])->name('home');
|
Route::get('/', [HomeController::class, 'index'])->name('home');
|
||||||
|
|
||||||
|
|
||||||
// Produk terlaris
|
// Produk terlaris
|
||||||
Route::get('/produk-terlaris', [ProductRecommendationController::class, 'index'])
|
Route::get('/produk-terlaris', [ProductRecommendationController::class, 'index'])
|
||||||
->name('products.best-selling');
|
->name('products.best-selling');
|
||||||
|
// Detail produk (public)
|
||||||
|
Route::get('/produk/{id}', [ProductController::class, 'publicShow'])->name('products.show');
|
||||||
|
|
||||||
// Contact Us
|
// Contact Us
|
||||||
Route::get('/contact', [ContactController::class, 'index'])->name('contact');
|
Route::get('/contact', [ContactController::class, 'index'])->name('contact');
|
||||||
|
|
@ -67,6 +72,23 @@
|
||||||
Route::get('/best-selling-products', [ProductRecommendationController::class, 'apiGetBestSelling'])
|
Route::get('/best-selling-products', [ProductRecommendationController::class, 'apiGetBestSelling'])
|
||||||
->name('best-selling-products');
|
->name('best-selling-products');
|
||||||
});
|
});
|
||||||
|
Route::middleware('auth')->group(function () {
|
||||||
|
// Profil
|
||||||
|
Route::get('/profile', [ProfileController::class, 'index'])->name('profile');
|
||||||
|
Route::post('/profile/update', [ProfileController::class, 'update'])->name('profile.update');
|
||||||
|
Route::post('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.password');
|
||||||
|
|
||||||
|
// Pesanan saya
|
||||||
|
Route::get('/pesanan-saya', [ProfileController::class, 'orders'])->name('orders.my');
|
||||||
|
Route::get('/pesanan-saya/{id}', [ProfileController::class, 'orderDetail'])->name('orders.detail');
|
||||||
|
|
||||||
|
Route::get('/orders/{id}/cancel', [ProfileController::class, 'cancelForm'])
|
||||||
|
->name('orders.cancel'); // ← nama harus orders.cancel
|
||||||
|
Route::post('/orders/{id}/cancel', [ProfileController::class, 'cancelStore'])
|
||||||
|
->name('orders.cancel.store'); // ← nama harus orders.cancel.store
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -76,27 +98,40 @@
|
||||||
|
|
||||||
Route::prefix('admin')->name('admin.')->group(function () {
|
Route::prefix('admin')->name('admin.')->group(function () {
|
||||||
|
|
||||||
// Admin login routes (accessible to guests)
|
Route::middleware('guest')->group(function () {
|
||||||
Route::middleware('guest')->group(function () {
|
Route::get('/login', function () {
|
||||||
Route::get('/login', function () {
|
return view('admin.login');
|
||||||
return view('admin.login');
|
})->name('login.form');
|
||||||
})->name('login.form');
|
|
||||||
|
|
||||||
Route::post('/login', [AdminController::class, 'login'])->name('login');
|
Route::post('/login', [AdminController::class, 'login'])->name('login');
|
||||||
|
|
||||||
|
Route::prefix('password')->name('password.')->group(function () { // ← ganti 'admin' jadi 'password'
|
||||||
|
Route::get('/forgot-password', [AdminForgotPasswordController::class, 'showForgotForm'])->name('request');
|
||||||
|
Route::post('/forgot-password', [AdminForgotPasswordController::class, 'sendOtp'])->name('email');
|
||||||
|
Route::get('/verify-otp', [AdminForgotPasswordController::class, 'showVerifyOtpForm'])->name('verify-otp-form');
|
||||||
|
Route::post('/verify-otp', [AdminForgotPasswordController::class, 'verifyOtp'])->name('verify-otp');
|
||||||
|
Route::post('/resend-otp', [AdminForgotPasswordController::class, 'resendOtp'])->name('resend-otp');
|
||||||
|
Route::get('/reset-password', [AdminForgotPasswordController::class, 'showResetForm'])->name('reset-form');
|
||||||
|
Route::post('/reset-password', [AdminForgotPasswordController::class, 'resetPassword'])->name('update');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Protected admin routes (requires authentication)
|
// Protected admin routes (requires authentication)
|
||||||
Route::middleware(['auth', 'web'])->group(function () {
|
Route::middleware(['auth', 'is_admin'])->group(function () {
|
||||||
|
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
Route::get('/dashboard', function () {
|
Route::get('/dashboard', [AdminController::class, 'dashboard'])->name('dashboard');
|
||||||
$totalProducts = \App\Models\Product::count();
|
// Di dalam group admin (bersama route lainnya):
|
||||||
$activeProducts = \App\Models\Product::where('status', 'aktif')->count();
|
Route::get('/laporan', [AdminController::class, 'laporan'])->name('laporan');
|
||||||
$totalSold = \App\Models\Product::sum('sold') ?? 0;
|
Route::get('/laporan/export', function() {
|
||||||
$lowStock = \App\Models\Product::where('stock', '<=', 10)->count();
|
return redirect()->route('admin.laporan')->with('info', 'Fitur export sedang dalam pengembangan.');
|
||||||
|
})->name('laporan.export');
|
||||||
|
Route::get('/transaksis/{id}', [TransaksiController::class, 'show'])->name('transaksis.show');
|
||||||
|
// nama lengkapnya: admin.transaksis.show
|
||||||
|
|
||||||
return view('admin.dashboard', compact('totalProducts', 'activeProducts', 'totalSold', 'lowStock'));
|
// HAPUS line 136 yang lama:
|
||||||
})->name('dashboard');
|
// Route::get('/admin/reports', [AdminController::class, 'reports'])->name('admin.reports');
|
||||||
|
|
||||||
// Product CRUD Routes
|
// Product CRUD Routes
|
||||||
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
|
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
|
||||||
|
|
@ -118,9 +153,16 @@
|
||||||
Route::post('/transaksis/{id}/cancel', [TransaksiController::class, 'cancel'])->name('transaksis.cancel');
|
Route::post('/transaksis/{id}/cancel', [TransaksiController::class, 'cancel'])->name('transaksis.cancel');
|
||||||
Route::get('/transaksis-export', [TransaksiController::class, 'export'])->name('transaksis.export');
|
Route::get('/transaksis-export', [TransaksiController::class, 'export'])->name('transaksis.export');
|
||||||
Route::get('/transaksis/{id}/print', [TransaksiController::class, 'printInvoice'])->name('transaksis.print');
|
Route::get('/transaksis/{id}/print', [TransaksiController::class, 'printInvoice'])->name('transaksis.print');
|
||||||
|
Route::post('/orders/{id}/update-status', [TransaksiController::class, 'updateStatus'])->name('orders.updateStatus');
|
||||||
|
// Cancellation Admin Routes
|
||||||
|
Route::get('/cancellations', [TransaksiController::class, 'cancellations'])
|
||||||
|
->name('cancellations');
|
||||||
|
Route::post('/cancellations/{id}/approve', [TransaksiController::class, 'approveCancellation'])
|
||||||
|
->name('cancellations.approve');
|
||||||
|
Route::post('/cancellations/{id}/reject', [TransaksiController::class, 'rejectCancellation'])
|
||||||
|
->name('cancellations.reject');
|
||||||
|
|
||||||
|
// Route::get('/users', [UserController::class, 'index'])->name('users.index');
|
||||||
Route::get('/users', [UserController::class, 'index'])->name('users.index');
|
|
||||||
Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
|
Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
|
||||||
Route::post('/users', [UserController::class, 'store'])->name('users.store');
|
Route::post('/users', [UserController::class, 'store'])->name('users.store');
|
||||||
Route::get('/users/{user}', [UserController::class, 'show'])->name('users.show');
|
Route::get('/users/{user}', [UserController::class, 'show'])->name('users.show');
|
||||||
|
|
@ -128,7 +170,6 @@
|
||||||
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
|
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
|
||||||
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
|
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
|
||||||
|
|
||||||
|
|
||||||
// Other admin routes
|
// Other admin routes
|
||||||
Route::get('/orders', [AdminController::class, 'orders'])->name('orders');
|
Route::get('/orders', [AdminController::class, 'orders'])->name('orders');
|
||||||
Route::get('/users', [AdminController::class, 'users'])->name('users');
|
Route::get('/users', [AdminController::class, 'users'])->name('users');
|
||||||
|
|
@ -138,6 +179,29 @@
|
||||||
// Admin logout
|
// Admin logout
|
||||||
Route::post('/logout', [AdminController::class, 'logout'])->name('logout');
|
Route::post('/logout', [AdminController::class, 'logout'])->name('logout');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}); // ← Penutup admin group yang benar (HANYA SATU)
|
}); // ← Penutup admin group yang benar (HANYA SATU)
|
||||||
|
|
||||||
|
//lupa password
|
||||||
|
Route::get('/forgot-password', [ForgotPasswordController::class, 'showForgotForm'])
|
||||||
|
->name('password.request');
|
||||||
|
|
||||||
|
Route::post('/forgot-password', [ForgotPasswordController::class, 'sendOtp'])
|
||||||
|
->name('password.email');
|
||||||
|
|
||||||
|
Route::get('/verify-otp', [ForgotPasswordController::class, 'showVerifyOtpForm'])
|
||||||
|
->name('password.verify-otp-form');
|
||||||
|
|
||||||
|
Route::post('/verify-otp', [ForgotPasswordController::class, 'verifyOtp'])
|
||||||
|
->name('password.verify-otp');
|
||||||
|
|
||||||
|
Route::post('/resend-otp', [ForgotPasswordController::class, 'resendOtp'])
|
||||||
|
->name('password.resend-otp');
|
||||||
|
|
||||||
|
Route::get('/reset-password', [ForgotPasswordController::class, 'showResetForm'])
|
||||||
|
->name('password.reset-form');
|
||||||
|
|
||||||
|
Route::post('/reset-password', [ForgotPasswordController::class, 'resetPassword'])
|
||||||
|
->name('password.update');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue