perbaikan responsive

belum100%
This commit is contained in:
bayughazali 2026-05-20 00:08:47 +07:00
parent b68eca055b
commit b5acd49443
17 changed files with 8489 additions and 4008 deletions

View File

@ -82,8 +82,9 @@ public function showVerifyOtpForm(Request $request)
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
// STEP 2 — Proses: verifikasi OTP // STEP 2 — Proses: verifikasi OTP
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
public function verifyOtp(Request $request) // STEP 2 — Proses: verifikasi OTP
{ public function verifyOtp(Request $request)
{
$request->validate([ $request->validate([
'otp' => ['required', 'digits:6'], 'otp' => ['required', 'digits:6'],
], [ ], [
@ -104,14 +105,18 @@ public function verifyOtp(Request $request)
return back()->with('error', 'Kode OTP tidak ditemukan. Silakan kirim ulang.'); return back()->with('error', 'Kode OTP tidak ditemukan. Silakan kirim ulang.');
} }
if (Carbon::now()->diffInMinutes(Carbon::parse($record->created_at)) > 10) { // ✅ CEK EXPIRED — hapus record & paksa user kirim ulang
if (Carbon::now()->diffInSeconds(Carbon::parse($record->created_at)) >= 180) {
DB::table('password_reset_tokens')->where('email', $email)->delete(); DB::table('password_reset_tokens')->where('email', $email)->delete();
return back()->with('error', 'Kode OTP sudah kadaluarsa. Silakan kirim ulang kode.'); return back()->with('otp_expired', true) // flag khusus untuk view
->with('error', 'Kode OTP sudah kadaluarsa (3 menit). Silakan kirim ulang kode.');
} }
if (!Hash::check($request->otp, $record->token)) { if (!Hash::check($request->otp, $record->token)) {
return back()->with('error', 'Kode OTP salah. Periksa kembali kode yang Anda masukkan.'); return back()
} ->with('error', 'Kode OTP salah. Waktu berkurang 30 detik.')
->with('otp_wrong', true); // ← flag untuk kurangi timer di frontend
}
$verifiedToken = Str::random(60); $verifiedToken = Str::random(60);
DB::table('password_reset_tokens') DB::table('password_reset_tokens')
@ -122,8 +127,7 @@ public function verifyOtp(Request $request)
return redirect()->route('admin.password.reset-form') return redirect()->route('admin.password.reset-form')
->with('success', 'Kode berhasil diverifikasi! Silakan buat password baru.'); ->with('success', 'Kode berhasil diverifikasi! Silakan buat password baru.');
} }
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
// STEP 2 — Kirim ulang OTP // STEP 2 — Kirim ulang OTP
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
@ -182,8 +186,11 @@ public function showResetForm(Request $request)
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
// STEP 3 — Proses: simpan password baru // STEP 3 — Proses: simpan password baru
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
public function resetPassword(Request $request) // ─────────────────────────────────────────────────────────
{ // STEP 3 — Proses: simpan password baru
// ─────────────────────────────────────────────────────────
public function resetPassword(Request $request)
{
$request->validate([ $request->validate([
'password' => ['required', 'min:8', 'confirmed', 'regex:/^\S+$/'], 'password' => ['required', 'min:8', 'confirmed', 'regex:/^\S+$/'],
'password_confirmation' => ['required'], 'password_confirmation' => ['required'],
@ -209,6 +216,15 @@ public function resetPassword(Request $request)
->with('error', 'Token tidak valid. Silakan ulangi dari awal.'); ->with('error', 'Token tidak valid. Silakan ulangi dari awal.');
} }
// ✅ CEK PASSWORD LAMA — tolak jika sama dengan password sekarang
$admin = User::where('email', $email)->where('is_admin', true)->first();
if ($admin && Hash::check($request->password, $admin->password)) {
return back()->withErrors([
'password' => 'Password baru tidak boleh sama dengan password yang sedang digunakan.',
])->withInput();
}
User::where('email', $email) User::where('email', $email)
->where('is_admin', true) ->where('is_admin', true)
->update(['password' => Hash::make($request->password)]); ->update(['password' => Hash::make($request->password)]);
@ -218,5 +234,5 @@ public function resetPassword(Request $request)
return redirect()->route('admin.login.form') return redirect()->route('admin.login.form')
->with('success', 'Password admin berhasil diubah! Silakan login dengan password baru.'); ->with('success', 'Password admin berhasil diubah! Silakan login dengan password baru.');
} }
} }

View File

@ -0,0 +1,23 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class FonnteService
{
public function sendOTP($nomor, $otp)
{
// Format nomor: 08xxx → 628xxx
$nomor = '62' . ltrim($nomor, '0');
$response = Http::withHeaders([
'Authorization' => env('FONNTE_TOKEN'),
])->post('https://api.fonnte.com/send', [
'target' => $nomor,
'message' => "Kode OTP reset password Anda: *$otp*\n\nBerlaku selama 10 menit. Jangan berikan kode ini kepada siapapun.",
]);
return $response->json();
}
}

View File

@ -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('users', function (Blueprint $table) {
$table->string('no_hp')->nullable()->after('email');
});
}
/**
* Reverse the migrations.
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('no_hp');
});
}
};

View File

@ -3,181 +3,544 @@
<head> <head>
<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">
<title>Pengajuan Pembatalan - Admin</title> <title>Pengajuan Pembatalan - Bibit Cabai Admin</title>
<meta name="csrf-token" content="{{ csrf_token() }}"> <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 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"> <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 { font-family: 'Segoe UI', sans-serif; background: #f8f9fa; }
.admin-container { display: flex; min-height: 100vh; }
:root {
--sidebar-width: 260px;
--green-dark: #15803d;
--green-main: #16a34a;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f1f5f2;
overflow-x: hidden;
}
/* ===== SIDEBAR ===== */
.sidebar { .sidebar {
width: 280px; background: linear-gradient(180deg, #28a745, #20c997); width: var(--sidebar-width);
color: white; min-height: 100vh; position: fixed; top: 0; left: 0; z-index: 1000; background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
color: white;
min-height: 100vh;
position: fixed;
top: 0; left: 0;
z-index: 1050;
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
} }
.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 {
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; } padding: 24px 20px 20px;
.sidebar-menu { padding: 20px 0; } text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
.sidebar-header .brand-icon { font-size: 2rem; margin-bottom: 6px; display: block; }
.sidebar-header h3 { font-size: 1.35rem; font-weight: 700; margin-bottom: 2px; }
.sidebar-header .subtitle { opacity: 0.65; font-size: 0.78rem; margin-bottom: 6px; }
.sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.sidebar-menu { padding: 12px 0; flex: 1; }
.menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item { .menu-item {
display: block; padding: 15px 25px; color: white; display: flex;
text-decoration: none; transition: all 0.3s; border-left: 4px solid transparent; align-items: center;
gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none;
transition: all 0.2s;
border-left: 3px solid transparent;
font-size: 0.9rem;
} }
.menu-item:hover, .menu-item.active {
background: rgba(255,255,255,0.2); color: white; .menu-item i { width: 18px; font-size: 0.95rem; flex-shrink: 0; text-align: center; }
border-left-color: #fff; transform: translateX(5px);
.menu-item:hover {
background: rgba(255,255,255,0.1);
color: white;
text-decoration: none;
border-left-color: rgba(255,255,255,0.5);
} }
.menu-item i { width: 20px; margin-right: 10px; }
.main-content { margin-left: 280px; flex: 1; } .menu-item.active {
.admin-header { background: rgba(255,255,255,0.15);
background: white; padding: 20px 30px; color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-bottom: 1px solid #dee2e6; border-left-color: #fff;
font-weight: 600;
} }
.content-area { padding: 30px; }
.badge-status { padding: 4px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; } .sidebar-footer {
.badge-pending { background: #fff3cd; color: #856404; } padding: 16px 20px;
.badge-approved { background: #d4edda; color: #155724; } border-top: 1px solid rgba(255,255,255,0.12);
.badge-rejected { background: #f8d7da; color: #721c24; } flex-shrink: 0;
.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; } .btn-logout {
.table td { vertical-align: middle; } width: 100%;
background: rgba(255,255,255,0.12);
color: white;
border: 1.5px solid rgba(255,255,255,0.3);
padding: 9px 16px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.action-btn { padding: 5px 12px; border-radius: 6px; font-size: 0.8rem; font-weight: 600; border: none; cursor: pointer; } .btn-logout:hover {
.action-btn.btn-success { background-color: #28a745 !important; color: white !important; opacity: 1 !important; } background: rgba(255,255,255,0.22);
.action-btn.btn-danger { background-color: #dc3545 !important; color: white !important; opacity: 1 !important; } border-color: rgba(255,255,255,0.5);
color: white;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content {
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== TOPBAR ===== */
.topbar {
background: white;
height: 64px;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 0 #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.topbar-left { display: flex; align-items: center; gap: 14px; }
.hamburger-btn {
display: none;
background: none;
border: none;
color: #374151;
font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
.hamburger-btn:hover { background: #f3f4f6; }
.topbar-breadcrumb { font-size: 0.8rem; color: #6b7280; margin-bottom: 2px; }
.topbar-breadcrumb a { color: var(--green-main); text-decoration: none; }
.topbar-title h2 { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.topbar-title p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.btn-back-website {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
border: none;
padding: 9px 18px;
border-radius: 8px;
font-weight: 600;
font-size: 0.825rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.2s;
white-space: nowrap;
}
.btn-back-website:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220,38,38,0.35);
color: white;
text-decoration: none;
}
/* ===== CONTENT AREA ===== */
.content-area { padding: 20px 24px 24px; flex: 1; }
/* ===== STATS GRID ===== */
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 20px;
}
.stat-card {
background: white;
border-radius: 12px;
padding: 18px 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
display: flex;
justify-content: space-between;
align-items: center;
border-left: 4px solid;
}
.stat-card.warning { border-left-color: #d97706; }
.stat-card.success { border-left-color: var(--green-main); }
.stat-card.danger { border-left-color: #dc2626; }
.stat-label {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 4px;
}
.stat-card.warning .stat-label { color: #d97706; }
.stat-card.success .stat-label { color: var(--green-main); }
.stat-card.danger .stat-label { color: #dc2626; }
.stat-value { font-size: 1.5rem; font-weight: 800; color: #111827; }
.stat-icon { font-size: 1.75rem; color: #e5e7eb; }
/* ===== TABLE CARD ===== */
.table-card {
background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
overflow: hidden;
}
.table-card-header {
padding: 18px 20px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.section-title {
font-size: 1rem;
font-weight: 700;
color: #111827;
display: flex;
align-items: center;
gap: 8px;
}
/* ===== TABLE ===== */
.data-table { width: 100%; border-collapse: collapse; }
.data-table th, .data-table td {
padding: 11px 14px;
text-align: left;
border-bottom: 1px solid #f3f4f6;
font-size: 0.875rem;
vertical-align: middle;
}
.data-table th {
background: #f9fafb;
font-weight: 600;
color: #6b7280;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
}
.data-table tbody tr:hover { background: #f9fafb; }
/* ===== BADGES ===== */
.badge-status {
padding: 4px 10px;
border-radius: 12px;
font-size: 0.72rem;
font-weight: 600;
display: inline-block;
}
.badge-pending { background: #fef3c7; color: #92400e; }
.badge-approved { background: #dcfce7; color: #166534; }
.badge-rejected { background: #fee2e2; color: #991b1b; }
/* ===== ACTION BUTTONS ===== */
.btn-action {
padding: 6px 12px;
border: none;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn-approve { background: #16a34a; color: white; }
.btn-reject { background: #dc2626; color: white; }
.btn-label-approved { background: #dcfce7; color: #166534; cursor: default; }
.btn-label-rejected { background: #fee2e2; color: #991b1b; cursor: default; }
.btn-action:hover:not([style*="cursor:default"]) {
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
color: inherit;
}
/* ===== TABLE FOOTER ===== */
.table-footer {
padding: 14px 20px;
border-top: 1px solid #f3f4f6;
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
/* ===== MOBILE CARDS ===== */
.mobile-card-list { display: none; }
.cancel-card {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
}
.cancel-card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
}
.cancel-card-invoice { font-weight: 700; font-size: 0.9rem; color: #111827; }
.cancel-card-amount { font-size: 0.78rem; color: #9ca3af; margin-top: 2px; }
.cancel-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 10px;
font-size: 0.8rem;
}
.cancel-card-meta-item .label { color: #9ca3af; font-size: 0.72rem; text-transform: uppercase; font-weight: 600; margin-bottom: 2px; }
.cancel-card-meta-item .value { color: #374151; }
.cancel-card-reason {
background: #f9fafb;
border-radius: 8px;
padding: 10px 12px;
font-size: 0.8rem;
color: #374151;
margin-bottom: 12px;
}
.cancel-card-reason .reason-label { font-weight: 700; color: #6b7280; font-size: 0.72rem; text-transform: uppercase; margin-bottom: 4px; }
.cancel-card-actions { display: flex; gap: 8px; }
.cancel-card-actions .btn-action { flex: 1; justify-content: center; }
/* ===== FADE IN ===== */
.fade-in { animation: fadeIn 0.3s ease; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ===== RESPONSIVE ===== */
@media (max-width: 900px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.2); }
.main-content { margin-left: 0; }
.hamburger-btn { display: flex; align-items: center; justify-content: center; }
}
@media (max-width: 767px) {
.desktop-table-wrap { display: none; }
.mobile-card-list { display: block; }
.stats-grid { grid-template-columns: 1fr 1fr; }
.stats-grid .stat-card:last-child { grid-column: span 2; }
.content-area { padding: 14px 14px 20px; }
.topbar { padding: 0 14px; }
.btn-back-website span { display: none; }
.btn-back-website { padding: 9px 12px; }
.table-footer { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 400px) {
.stats-grid { grid-template-columns: 1fr; }
.stats-grid .stat-card:last-child { grid-column: span 1; }
}
</style> </style>
</head> </head>
<body> <body>
<a href="{{ route('home') }}" class="back-button"> <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
</a>
<div class="admin-container"> <!-- Sidebar -->
{{-- Sidebar --}} <nav class="sidebar" id="sidebar">
<nav class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<h3>🌱 Bibit Cabai</h3> <span class="brand-icon">🌱</span>
<p>Admin Dashboard</p> <h3>Bibit Cabai</h3>
<small class="text-light">{{ Auth::user()->name ?? 'Admin' }}</small> <p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div> </div>
<div class="sidebar-menu"> <div class="sidebar-menu">
<div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.dashboard') }}" class="menu-item"> <a href="{{ route('admin.dashboard') }}" class="menu-item">
<i class="fas fa-tachometer-alt"></i>Dashboard <i class="fas fa-tachometer-alt"></i><span>Dashboard</span>
</a> </a>
<a href="{{ route('admin.products.index') }}" class="menu-item"> <a href="{{ route('admin.products.index') }}" class="menu-item">
<i class="fas fa-seedling"></i>Kelola Produk <i class="fas fa-seedling"></i><span>Kelola Produk</span>
</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><span>Pesanan</span>
</a> </a>
<a href="{{ route('admin.cancellations') }}" class="menu-item active"> <a href="{{ route('admin.cancellations') }}" class="menu-item active">
<i class="fas fa-times-circle"></i>Pengajuan Batal <i class="fas fa-times-circle"></i><span>Pengajuan Batal</span>
</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><span>Pengguna</span>
</a> </a>
<a href="{{ route('admin.laporan') }}" 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><span>Laporan</span>
</a> </a>
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"> </div>
<i class="fas fa-cog"></i>Pengaturan
</a> --> <div class="sidebar-footer">
<div class="mt-4 px-3">
<form method="POST" action="{{ route('admin.logout') }}"> <form method="POST" action="{{ route('admin.logout') }}">
@csrf @csrf
<button type="submit" class="btn btn-outline-light btn-sm w-100"> <button type="submit" class="btn-logout">
<i class="fas fa-sign-out-alt me-2"></i>Logout <i class="fas fa-sign-out-alt"></i><span>Logout</span>
</button> </button>
</form> </form>
</div> </div>
</div>
</nav> </nav>
{{-- Main Content --}} <!-- Main Content -->
<div class="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"> <!-- Topbar -->
<header class="topbar">
<div class="topbar-left">
<button class="hamburger-btn" onclick="toggleSidebar()" aria-label="Toggle menu">
<i class="fas fa-bars"></i>
</button>
<div class="topbar-title">
<div class="topbar-breadcrumb">
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
&nbsp;/&nbsp;Pengajuan Pembatalan
</div>
<h2>Pengajuan Pembatalan Pesanan</h2>
<p>Kelola semua permintaan pembatalan dari pelanggan.</p>
</div>
</div>
<a href="{{ route('home') }}" class="btn-back-website">
<i class="fas fa-arrow-left"></i>
<span>Kembali ke Website</span>
</a>
</header>
<!-- Content -->
<div class="content-area fade-in">
@if(session('success')) @if(session('success'))
<div class="alert alert-success alert-dismissible fade show"> <div class="alert alert-success alert-dismissible fade show mb-4" role="alert">
<i class="fas fa-check-circle me-2"></i>{{ session('success') }} <i class="fas fa-check-circle me-2"></i>{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div> </div>
@endif @endif
{{-- Stats --}} <!-- Stats -->
<div class="row g-3 mb-4"> <div class="stats-grid">
<div class="col-md-4"> <div class="stat-card warning">
<div class="card p-3 border-start border-warning border-4">
<div class="d-flex justify-content-between align-items-center">
<div> <div>
<div class="text-warning fw-bold small">MENUNGGU REVIEW</div> <div class="stat-label">Menunggu Review</div>
<div class="fs-3 fw-bold">{{ $cancellations->where('status','pending')->count() }}</div> <div class="stat-value">{{ $cancellations->where('status','pending')->count() }}</div>
</div> </div>
<i class="fas fa-clock fa-2x text-muted"></i> <div class="stat-icon"><i class="fas fa-clock"></i></div>
</div> </div>
</div> <div class="stat-card success">
</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>
<div class="text-success fw-bold small">DISETUJUI</div> <div class="stat-label">Disetujui</div>
<div class="fs-3 fw-bold">{{ $cancellations->where('status','approved')->count() }}</div> <div class="stat-value">{{ $cancellations->where('status','approved')->count() }}</div>
</div> </div>
<i class="fas fa-check-circle fa-2x text-muted"></i> <div class="stat-icon"><i class="fas fa-check-circle"></i></div>
</div> </div>
</div> <div class="stat-card danger">
</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>
<div class="text-danger fw-bold small">DITOLAK</div> <div class="stat-label">Ditolak</div>
<div class="fs-3 fw-bold">{{ $cancellations->where('status','rejected')->count() }}</div> <div class="stat-value">{{ $cancellations->where('status','rejected')->count() }}</div>
</div>
<i class="fas fa-times-circle fa-2x text-muted"></i>
</div>
</div> </div>
<div class="stat-icon"><i class="fas fa-times-circle"></i></div>
</div> </div>
</div> </div>
{{-- Tabel --}} <!-- Table Card -->
<div class="card"> <div class="table-card">
<div class="card-header py-3"> <div class="table-card-header">
<h6 class="mb-0 fw-bold text-success"> <div class="section-title">
<i class="fas fa-times-circle me-2"></i> <i class="fas fa-times-circle text-success"></i>
Daftar Pengajuan Pembatalan ({{ $cancellations->total() }}) Daftar Pengajuan Pembatalan ({{ $cancellations->total() }})
</h6>
</div> </div>
<div class="card-body p-0"> </div>
<div class="table-responsive">
<table class="table table-hover mb-0"> <!-- DESKTOP TABLE -->
<div class="desktop-table-wrap table-responsive">
<table class="data-table">
<thead> <thead>
<tr> <tr>
<th class="ps-3">Invoice</th> <th class="ps-3">Invoice</th>
@ -193,68 +556,62 @@
@forelse($cancellations as $item) @forelse($cancellations as $item)
<tr> <tr>
<td class="ps-3"> <td class="ps-3">
<strong class="d-block">{{ $item->transaksi->invoice_number ?? '-' }}</strong> <strong class="d-block" style="color:#111827;">{{ $item->transaksi->invoice_number ?? '-' }}</strong>
<small class="text-muted"> <small style="color:#9ca3af;">
Rp{{ number_format($item->transaksi->total_amount ?? 0, 0, ',', '.') }} Rp{{ number_format($item->transaksi->total_amount ?? 0, 0, ',', '.') }}
</small> </small>
</td> </td>
<td> <td>
<strong>{{ $item->user->name ?? '-' }}</strong><br> <strong style="color:#111827;">{{ $item->user->name ?? '-' }}</strong><br>
<small class="text-muted">{{ $item->user->email ?? '-' }}</small> <small style="color:#9ca3af;">{{ $item->user->email ?? '-' }}</small>
</td>
<td style="max-width:160px;">
<small style="color:#374151;">{{ $item->reason }}</small>
</td> </td>
<td style="max-width:180px;"> <td style="max-width:180px;">
<small>{{ $item->reason }}</small> <small style="color:#6b7280;">{{ $item->description ?? '-' }}</small>
</td>
<td style="max-width:200px;">
<small class="text-muted">{{ $item->description ?? '-' }}</small>
</td> </td>
<td> <td>
<small>{{ $item->created_at->format('d M Y') }}</small><br> <small style="color:#374151;">{{ $item->created_at->format('d M Y') }}</small><br>
<small class="text-muted">{{ $item->created_at->format('H:i') }}</small> <small style="color:#9ca3af;">{{ $item->created_at->format('H:i') }}</small>
</td> </td>
<td> <td>
<span class="badge-status badge-{{ $item->status }}"> <span class="badge-status badge-{{ $item->status }}">
{{ $item->status === 'pending' ? 'Menunggu' : ($item->status === 'approved' ? 'Disetujui' : 'Ditolak') }} {{ $item->status === 'pending' ? 'Menunggu' : ($item->status === 'approved' ? 'Disetujui' : 'Ditolak') }}
</span> </span>
@if($item->reviewed_at) @if($item->reviewed_at)
<br><small class="text-muted">{{ $item->reviewed_at->format('d M Y') }}</small> <br><small style="color:#9ca3af;">{{ $item->reviewed_at->format('d M Y') }}</small>
@endif @endif
</td> </td>
<td class="text-center"> <td class="text-center">
@if($item->status === 'pending') @if($item->status === 'pending')
{{-- Tombol Setujui --}}
<form action="{{ route('admin.cancellations.approve', $item->id) }}" method="POST" <form action="{{ route('admin.cancellations.approve', $item->id) }}" method="POST"
class="d-inline" class="d-inline"
onsubmit="return confirm('Setujui pembatalan pesanan {{ $item->transaksi->invoice_number ?? '' }}?')"> onsubmit="return confirm('Setujui pembatalan pesanan {{ $item->transaksi->invoice_number ?? '' }}?')">
@csrf @csrf
<textarea name="admin_note" style="display:none"> disetujui</textarea> <textarea name="admin_note" style="display:none"> disetujui</textarea>
<button type="submit" class="action-btn btn-success text-white me-1"> <button type="submit" class="btn-action btn-approve me-1">
<i class="fas fa-check me-1"></i>Setujui <i class="fas fa-check"></i>Setujui
</button> </button>
</form> </form>
<button type="button" class="btn-action btn-reject"
{{-- Tombol Tolak --}}
<button type="button"
class="action-btn btn-danger text-white"
onclick="showRejectModal({{ $item->id }}, '{{ $item->transaksi->invoice_number ?? '' }}')"> onclick="showRejectModal({{ $item->id }}, '{{ $item->transaksi->invoice_number ?? '' }}')">
<i class="fas fa-times me-1"></i>Tolak <i class="fas fa-times"></i>Tolak
</button> </button>
@else @elseif($item->status === 'approved')
@if($item->status === 'approved') <span class="btn-action btn-label-approved">Disetujui</span>
<span class="action-btn btn-success">Disetujui</span>
@elseif($item->status === 'rejected') @elseif($item->status === 'rejected')
<span class="action-btn btn-danger">Ditolak</span> <span class="btn-action btn-label-rejected">Ditolak</span>
@else @else
<span class="text-muted">-</span> <span style="color:#9ca3af;">-</span>
@endif
@endif @endif
</td> </td>
</tr> </tr>
@empty @empty
<tr> <tr>
<td colspan="7" class="text-center py-5"> <td colspan="7" style="text-align:center; padding:50px 20px; color:#9ca3af;">
<i class="fas fa-inbox fa-3x text-muted mb-3 d-block"></i> <i class="fas fa-inbox" style="font-size:2.5rem; display:block; margin-bottom:12px; opacity:0.3;"></i>
<p class="text-muted">Belum ada pengajuan pembatalan</p> Belum ada pengajuan pembatalan
</td> </td>
</tr> </tr>
@endforelse @endforelse
@ -262,28 +619,94 @@ class="action-btn btn-danger text-white"
</table> </table>
</div> </div>
{{-- Pagination --}} <!-- MOBILE CARD LIST -->
<div class="mobile-card-list" style="padding:16px;">
@forelse($cancellations as $item)
<div class="cancel-card">
<div class="cancel-card-top">
<div>
<div class="cancel-card-invoice">{{ $item->transaksi->invoice_number ?? '-' }}</div>
<div class="cancel-card-amount">Rp{{ number_format($item->transaksi->total_amount ?? 0, 0, ',', '.') }}</div>
</div>
<span class="badge-status badge-{{ $item->status }}">
{{ $item->status === 'pending' ? 'Menunggu' : ($item->status === 'approved' ? 'Disetujui' : 'Ditolak') }}
</span>
</div>
<div class="cancel-card-meta">
<div class="cancel-card-meta-item">
<div class="label">Pelanggan</div>
<div class="value">{{ $item->user->name ?? '-' }}</div>
</div>
<div class="cancel-card-meta-item">
<div class="label">Tanggal</div>
<div class="value">{{ $item->created_at->format('d M Y') }}</div>
</div>
<div class="cancel-card-meta-item" style="grid-column:span 2;">
<div class="label">Email</div>
<div class="value">{{ $item->user->email ?? '-' }}</div>
</div>
</div>
<div class="cancel-card-reason">
<div class="reason-label">Alasan</div>
{{ $item->reason }}
@if($item->description)
<div style="margin-top:6px; color:#6b7280; font-size:0.78rem;">{{ $item->description }}</div>
@endif
</div>
@if($item->status === 'pending')
<div class="cancel-card-actions">
<form action="{{ route('admin.cancellations.approve', $item->id) }}" method="POST"
style="flex:1;"
onsubmit="return confirm('Setujui pembatalan pesanan {{ $item->transaksi->invoice_number ?? '' }}?')">
@csrf
<textarea name="admin_note" style="display:none"> disetujui</textarea>
<button type="submit" class="btn-action btn-approve" style="width:100%; justify-content:center;">
<i class="fas fa-check"></i>Setujui
</button>
</form>
<button type="button" class="btn-action btn-reject"
style="flex:1; justify-content:center;"
onclick="showRejectModal({{ $item->id }}, '{{ $item->transaksi->invoice_number ?? '' }}')">
<i class="fas fa-times"></i>Tolak
</button>
</div>
@elseif($item->status === 'approved')
<span class="btn-action btn-label-approved" style="width:100%; justify-content:center; display:flex;">Disetujui</span>
@elseif($item->status === 'rejected')
<span class="btn-action btn-label-rejected" style="width:100%; justify-content:center; display:flex;">Ditolak</span>
@endif
</div>
@empty
<div style="text-align:center; padding:50px 20px; color:#9ca3af;">
<i class="fas fa-inbox" style="font-size:2.5rem; display:block; margin-bottom:12px; opacity:0.3;"></i>
Belum ada pengajuan pembatalan
</div>
@endforelse
</div>
<!-- Pagination -->
@if($cancellations->hasPages()) @if($cancellations->hasPages())
<div class="d-flex justify-content-between align-items-center p-3 border-top"> <div class="table-footer">
<small class="text-muted"> <small style="color:#6b7280;">
Menampilkan {{ $cancellations->firstItem() }} - {{ $cancellations->lastItem() }} Menampilkan {{ $cancellations->firstItem() }}{{ $cancellations->lastItem() }}
dari {{ $cancellations->total() }} pengajuan dari {{ $cancellations->total() }} pengajuan
</small> </small>
{{ $cancellations->links() }} <div>{{ $cancellations->links() }}</div>
</div> </div>
@endif @endif
</div> </div>
</div>
</div> </div>
</div> </div>
</div>
{{-- Modal Tolak --}} <!-- Modal Tolak -->
<div class="modal fade" id="rejectModal" tabindex="-1" aria-hidden="true"> <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-dialog modal-dialog-centered" style="max-width:420px;">
<div class="modal-content border-0" style="border-radius:14px; overflow:hidden;"> <div class="modal-content border-0" style="border-radius:14px; overflow:hidden;">
<div class="modal-header bg-danger text-white"> <div class="modal-header bg-danger text-white border-0">
<h6 class="modal-title fw-bold mb-0"> <h6 class="modal-title fw-bold mb-0">
<i class="fas fa-times-circle me-2"></i>Tolak Pengajuan <i class="fas fa-times-circle me-2"></i>Tolak Pengajuan
</h6> </h6>
@ -292,8 +715,8 @@ class="action-btn btn-danger text-white"
<form id="rejectForm" method="POST"> <form id="rejectForm" method="POST">
@csrf @csrf
<div class="modal-body p-4"> <div class="modal-body p-4">
<p class="mb-3">Pesanan: <strong id="rejectInvoice"></strong></p> <p class="mb-3" style="font-size:0.9rem;">Pesanan: <strong id="rejectInvoice"></strong></p>
<label class="form-label fw-semibold"> <label class="form-label fw-semibold" style="font-size:0.875rem;">
Alasan Penolakan <span class="text-danger">*</span> Alasan Penolakan <span class="text-danger">*</span>
</label> </label>
<textarea name="admin_note" class="form-control" rows="3" <textarea name="admin_note" class="form-control" rows="3"
@ -310,16 +733,35 @@ class="action-btn btn-danger text-white"
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<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> <script>
function showRejectModal(id, invoice) { function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.toggle('open');
overlay.classList.toggle('show');
document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
}
function closeSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.remove('open');
overlay.classList.remove('show');
document.body.style.overflow = '';
}
window.addEventListener('resize', function() {
if (window.innerWidth > 900) closeSidebar();
});
function showRejectModal(id, invoice) {
document.getElementById('rejectInvoice').textContent = invoice; document.getElementById('rejectInvoice').textContent = invoice;
document.getElementById('rejectForm').action = `/admin/cancellations/${id}/reject`; document.getElementById('rejectForm').action = `/admin/cancellations/${id}/reject`;
new bootstrap.Modal(document.getElementById('rejectModal')).show(); new bootstrap.Modal(document.getElementById('rejectModal')).show();
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -10,143 +10,551 @@
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { 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 */ :root {
.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-width: 260px;
.sidebar-header { padding: 25px 20px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.1); } --header-height: 64px;
.sidebar-header h3 { font-size: 1.8rem; font-weight: bold; margin-bottom: 5px; } --green-dark: #15803d;
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; } --green-main: #16a34a;
.sidebar-menu { padding: 20px 0; } --green-light: #22c55e;
.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 */ body {
.main-content { margin-left: 280px; flex: 1; min-height: 100vh; } font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
.admin-header { background: white; padding: 20px 30px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-bottom: 1px solid #dee2e6; } background: #f1f5f2;
.content-area { padding: 30px; } overflow-x: hidden;
}
/* Stats Cards */ /* ===== SIDEBAR ===== */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 25px; margin-bottom: 40px; } .sidebar {
.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; } width: var(--sidebar-width);
.stat-card:hover { transform: translateY(-5px); } background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
.stat-card.products { border-left-color: #28a745; } color: white;
.stat-card.orders { border-left-color: #007bff; } min-height: 100vh;
.stat-card.revenue { border-left-color: #ffc107; } position: fixed;
.stat-card.users { border-left-color: #6f42c1; } top: 0;
.stat-number { font-size: 2.5rem; font-weight: bold; margin-bottom: 8px; } left: 0;
.stat-label { color: #6c757d; font-size: 1rem; font-weight: 500; } z-index: 1050;
.stat-icon { float: right; font-size: 2.5rem; opacity: 0.3; margin-top: -10px; } transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
}
/* Back Button */ .sidebar-header {
.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; } padding: 24px 20px 20px;
.back-button:hover { background: linear-gradient(45deg, #c82333, #a71e2a); transform: translateY(-2px); color: white; text-decoration: none; } text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
/* Chart Card Header */ .sidebar-header .brand-icon {
.chart-card-header { font-size: 2rem;
margin-bottom: 6px;
display: block;
}
.sidebar-header h3 {
font-size: 1.35rem;
font-weight: 700;
margin-bottom: 2px;
letter-spacing: -0.3px;
}
.sidebar-header .subtitle {
opacity: 0.65;
font-size: 0.78rem;
margin-bottom: 6px;
}
.sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.sidebar-menu {
padding: 12px 0;
flex: 1;
}
.menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none;
transition: all 0.2s;
border-left: 3px solid transparent;
font-size: 0.9rem;
cursor: pointer;
}
.menu-item i {
width: 18px;
font-size: 0.95rem;
flex-shrink: 0;
text-align: center;
}
.menu-item:hover {
background: rgba(255,255,255,0.1);
color: white;
text-decoration: none;
border-left-color: rgba(255,255,255,0.5);
}
.menu-item.active {
background: rgba(255,255,255,0.15);
color: white;
border-left-color: #fff;
font-weight: 600;
}
.sidebar-footer {
padding: 16px 20px;
border-top: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
.btn-logout {
width: 100%;
background: rgba(255,255,255,0.12);
color: white;
border: 1.5px solid rgba(255,255,255,0.3);
padding: 9px 16px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.5);
color: white;
}
/* Sidebar Overlay (mobile) */
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content {
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== TOPBAR ===== */
.topbar {
background: white;
height: var(--header-height);
padding: 0 24px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap; box-shadow: 0 1px 0 #e5e7eb;
gap: 10px; position: sticky;
top: 0;
z-index: 100;
}
.topbar-left {
display: flex;
align-items: center;
gap: 14px;
}
.hamburger-btn {
display: none;
background: none;
border: none;
color: #374151;
font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
.hamburger-btn:hover { background: #f3f4f6; }
.topbar-title h2 {
font-size: 1.15rem;
font-weight: 700;
color: #111827;
margin: 0;
line-height: 1.2;
}
.topbar-title p {
font-size: 0.78rem;
color: #6b7280;
margin: 0;
}
.btn-back-website {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
border: none;
padding: 9px 18px;
border-radius: 8px;
font-weight: 600;
font-size: 0.825rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.2s;
white-space: nowrap;
}
.btn-back-website:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220,38,38,0.35);
color: white;
text-decoration: none;
}
/* ===== CONTENT AREA ===== */
.content-area {
padding: 24px;
flex: 1;
}
/* ===== STATS GRID ===== */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 28px;
}
.stat-card {
background: white;
padding: 22px 20px;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.04);
border-left: 4px solid;
transition: transform 0.25s ease, box-shadow 0.25s ease;
position: relative;
overflow: hidden;
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
}
.stat-card.products { border-left-color: #16a34a; }
.stat-card.orders { border-left-color: #2563eb; }
.stat-card.revenue { border-left-color: #d97706; }
.stat-card.users { border-left-color: #7c3aed; }
.stat-number {
font-size: 2rem;
font-weight: 800;
margin-bottom: 4px;
line-height: 1;
letter-spacing: -0.5px;
}
.stat-label {
color: #6b7280;
font-size: 0.85rem;
font-weight: 500;
}
.stat-icon {
position: absolute;
top: 18px;
right: 18px;
font-size: 2rem;
opacity: 0.12;
}
/* ===== CHART CARD ===== */
.chart-card {
background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.04);
overflow: hidden;
margin-bottom: 28px;
}
.chart-card-header {
background: linear-gradient(135deg, var(--green-main), var(--green-light));
padding: 16px 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.chart-card-header h5 {
color: white;
font-size: 0.975rem;
font-weight: 700;
margin: 0;
} }
/* Lihat Detail Button */
.btn-lihat-detail { .btn-lihat-detail {
background: rgba(255,255,255,0.2); background: rgba(255,255,255,0.2);
color: white; color: white;
border: 2px solid rgba(255,255,255,0.6); border: 1.5px solid rgba(255,255,255,0.55);
padding: 7px 18px; padding: 6px 16px;
border-radius: 20px; border-radius: 20px;
font-size: 0.85rem; font-size: 0.8rem;
font-weight: 600; font-weight: 600;
text-decoration: none; text-decoration: none;
transition: all 0.3s ease; transition: all 0.2s;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
white-space: nowrap; white-space: nowrap;
} }
.btn-lihat-detail:hover { .btn-lihat-detail:hover {
background: white; background: white;
color: #28a745; color: var(--green-main);
border-color: white; border-color: white;
text-decoration: none; text-decoration: none;
transform: translateY(-2px); transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15); box-shadow: 0 4px 10px rgba(0,0,0,0.12);
}
.chart-card-body {
padding: 20px;
} }
/* Empty State */ /* Empty State */
.empty-state { text-align: center; padding: 60px 20px; color: #6c757d; } .empty-state {
.empty-state i { font-size: 4rem; margin-bottom: 20px; opacity: 0.3; } text-align: center;
.empty-state h4 { font-size: 1.5rem; margin-bottom: 10px; } padding: 48px 20px;
.empty-state p { font-size: 1rem; margin-bottom: 20px; } color: #9ca3af;
}
@media (max-width: 768px) { .empty-state i { font-size: 3rem; margin-bottom: 16px; opacity: 0.35; display: block; }
.sidebar { width: 100%; position: relative; min-height: auto; } .empty-state h4 { font-size: 1.1rem; color: #6b7280; margin-bottom: 8px; }
.main-content { margin-left: 0; } .empty-state p { font-size: 0.875rem; margin-bottom: 16px; }
.stats-grid { grid-template-columns: 1fr; }
.btn-buka-laporan {
background: var(--green-main);
color: white;
border: none;
padding: 9px 20px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.2s;
}
.btn-buka-laporan:hover {
background: var(--green-dark);
color: white;
text-decoration: none;
transform: translateY(-1px);
}
/* ===== RESPONSIVE ===== */
/* Large tablets & small desktops */
@media (max-width: 1100px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Tablets (sidebar collapses) */
@media (max-width: 900px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0);
box-shadow: 4px 0 24px rgba(0,0,0,0.2);
}
.main-content {
margin-left: 0;
}
.hamburger-btn {
display: flex;
align-items: center;
justify-content: center;
}
}
/* Mobile phones */
@media (max-width: 600px) {
.stats-grid {
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.stat-card {
padding: 16px 14px;
}
.stat-number {
font-size: 1.6rem;
}
.stat-label {
font-size: 0.78rem;
}
.stat-icon {
font-size: 1.5rem;
}
.content-area {
padding: 16px;
}
.topbar {
padding: 0 16px;
}
.btn-back-website span {
display: none;
}
.btn-back-website {
padding: 9px 12px;
}
.chart-card-header h5 {
font-size: 0.875rem;
}
}
@media (max-width: 380px) {
.stats-grid {
grid-template-columns: 1fr;
}
} }
</style> </style>
</head> </head>
<body> <body>
<a href="{{ route('home') }}" class="back-button">
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website <!-- Sidebar Overlay (mobile) -->
<div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<!-- Sidebar -->
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<span class="brand-icon">🌱</span>
<h3>Bibit Cabai</h3>
<p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div>
<div class="sidebar-menu">
<div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.dashboard') }}" class="menu-item {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a> </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' : '' }}"> <a href="{{ route('admin.products.index') }}" class="menu-item {{ request()->routeIs('admin.products.*') ? 'active' : '' }}">
<i class="fas fa-seedling"></i>Kelola Produk <i class="fas fa-seedling"></i>
<span>Kelola Produk</span>
</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>
<span>Pesanan</span>
</a> </a>
{{-- TAMBAHKAN DI SINI --}}
<a href="{{ route('admin.cancellations') }}" class="menu-item"> <a href="{{ route('admin.cancellations') }}" class="menu-item {{ request()->routeIs('admin.cancellations') ? 'active' : '' }}">
<i class="fas fa-times-circle"></i>Pengajuan Batal <i class="fas fa-times-circle"></i>
<span>Pengajuan Batal</span>
</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>
<span>Pengguna</span>
</a> </a>
<a href="{{ route('admin.laporan') }}" class="menu-item {{ request()->routeIs('admin.laporan') ? '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>
<span>Laporan</span>
</a> </a>
<!-- <a href="{{ route('admin.settings') }}" class="menu-item {{ request()->routeIs('admin.settings') ? 'active' : '' }}"> </div>
<i class="fas fa-cog"></i>Pengaturan
</a> --> <div class="sidebar-footer">
<div class="mt-4 px-3">
<form method="POST" action="{{ route('admin.logout') }}"> <form method="POST" action="{{ route('admin.logout') }}">
@csrf @csrf
<button type="submit" class="btn btn-outline-light btn-sm w-100" onclick="return confirm('Apakah Anda yakin ingin logout?')"> <button type="submit" class="btn-logout" onclick="return confirm('Apakah Anda yakin ingin logout?')">
<i class="fas fa-sign-out-alt me-2"></i>Logout <i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</button> </button>
</form> </form>
</div> </div>
</div>
</nav> </nav>
<!-- Main Content --> <!-- Main Content -->
<div class="main-content"> <div class="main-content" id="mainContent">
<div class="admin-header">
<h2>Dashboard</h2>
<p class="mb-0">Selamat datang di dashboard admin!</p>
</div>
<!-- Topbar -->
<header class="topbar">
<div class="topbar-left">
<button class="hamburger-btn" id="hamburgerBtn" onclick="toggleSidebar()" aria-label="Toggle menu">
<i class="fas fa-bars"></i>
</button>
<div class="topbar-title">
<h2>Dashboard</h2>
<p>Selamat datang di dashboard admin!</p>
</div>
</div>
<a href="{{ route('home') }}" class="btn-back-website">
<i class="fas fa-arrow-left"></i>
<span>Kembali ke Website</span>
</a>
</header>
<!-- Content -->
<div class="content-area"> <div class="content-area">
<!-- Stats Grid -->
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card products"> <div class="stat-card products">
<div class="stat-number text-success">{{ $stats['total_products'] }}</div> <div class="stat-number text-success">{{ $stats['total_products'] }}</div>
@ -170,19 +578,15 @@
</div> </div>
</div> </div>
<div class="row"> <!-- Chart Card -->
<div class="col-12 mb-4"> <div class="chart-card">
<div class="card">
<div class="card-header bg-success text-white">
<div class="chart-card-header"> <div class="chart-card-header">
<h5 class="mb-0">📈 Grafik Pendapatan Harian (30 Hari Terakhir)</h5> <h5>📈 Grafik Pendapatan Harian (30 Hari Terakhir)</h5>
{{-- Tombol Lihat Detail mengarah ke halaman laporan --}}
<a href="{{ route('admin.laporan') }}" class="btn-lihat-detail"> <a href="{{ route('admin.laporan') }}" class="btn-lihat-detail">
<i class="fas fa-external-link-alt"></i> Lihat Detail <i class="fas fa-external-link-alt"></i> Lihat Detail
</a> </a>
</div> </div>
</div> <div class="chart-card-body">
<div class="card-body">
@if(count($chartLabels) > 0) @if(count($chartLabels) > 0)
<canvas id="salesChart" height="80"></canvas> <canvas id="salesChart" height="80"></canvas>
@else @else
@ -190,39 +594,46 @@
<i class="fas fa-chart-line"></i> <i class="fas fa-chart-line"></i>
<h4>Belum Ada Data</h4> <h4>Belum Ada Data</h4>
<p>Grafik pendapatan akan ditampilkan setelah ada transaksi</p> <p>Grafik pendapatan akan ditampilkan setelah ada transaksi</p>
<a href="{{ route('admin.laporan') }}" class="btn btn-success"> <a href="{{ route('admin.laporan') }}" class="btn-buka-laporan">
<i class="fas fa-chart-bar me-2"></i>Buka Laporan <i class="fas fa-chart-bar"></i>Buka Laporan
</a> </a>
</div> </div>
@endif @endif
</div> </div>
</div> </div>
</div>
</div>
<!-- <div class="row"> </div><!-- /content-area -->
<div class="col-md-6 mb-4"> </div><!-- /main-content -->
<div class="card">
<div class="card-header bg-info text-white">
<h5 class="mb-0"> Aktivitas Terbaru</h5>
</div>
<div class="card-body">
<div class="empty-state">
<i class="fas fa-clock"></i>
<h4>Belum Ada Aktivitas</h4>
<p>Aktivitas terbaru akan ditampilkan di sini</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> -->
<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 src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script> <script>
// Sidebar toggle
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.toggle('open');
overlay.classList.toggle('show');
document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
}
function closeSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.remove('open');
overlay.classList.remove('show');
document.body.style.overflow = '';
}
// Close sidebar on resize if desktop
window.addEventListener('resize', function() {
if (window.innerWidth > 900) {
closeSidebar();
}
});
// Chart
@if(count($chartLabels) > 0) @if(count($chartLabels) > 0)
const ctx = document.getElementById('salesChart').getContext('2d'); const ctx = document.getElementById('salesChart').getContext('2d');
new Chart(ctx, { new Chart(ctx, {
@ -232,11 +643,12 @@
datasets: [{ datasets: [{
label: 'Penjualan (Rp)', label: 'Penjualan (Rp)',
data: {!! json_encode($chartData) !!}, data: {!! json_encode($chartData) !!},
borderColor: '#28a745', borderColor: '#16a34a',
backgroundColor: 'rgba(40, 167, 69, 0.1)', backgroundColor: 'rgba(22, 163, 74, 0.08)',
borderWidth: 2, borderWidth: 2.5,
pointBackgroundColor: '#28a745', pointBackgroundColor: '#16a34a',
pointRadius: 4, pointRadius: 4,
pointHoverRadius: 6,
tension: 0.4, tension: 0.4,
fill: true fill: true
}] }]

File diff suppressed because it is too large Load Diff

View File

@ -1,180 +1,335 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="id"> <html lang="id">
<head> <head>
<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">
<title>Orders Management - Bibit Cabai Admin</title> <title>Orders Management - Bibit Cabai Admin</title>
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<!-- 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">
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--sidebar-width: 260px;
--green-dark: #15803d;
--green-main: #16a34a;
}
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8f9fa; background: #f1f5f2;
overflow-x: hidden;
} }
.admin-container { /* ===== SIDEBAR ===== */
display: flex;
min-height: 100vh;
}
/* Sidebar */
.sidebar { .sidebar {
width: 280px; width: var(--sidebar-width);
background: linear-gradient(180deg, #28a745, #20c997); background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
color: white; color: white;
min-height: 100vh; min-height: 100vh;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: 1000; z-index: 1050;
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
} }
.sidebar-header { .sidebar-header {
padding: 25px 20px; padding: 24px 20px 20px;
text-align: center; text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.1); border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
} }
.sidebar-header h3 { .sidebar-header .brand-icon { font-size: 2rem; margin-bottom: 6px; display: block; }
font-size: 1.8rem; .sidebar-header h3 { font-size: 1.35rem; font-weight: 700; margin-bottom: 2px; }
font-weight: bold; .sidebar-header .subtitle { opacity: 0.65; font-size: 0.78rem; margin-bottom: 6px; }
margin-bottom: 5px; .sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
} }
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; } .sidebar-menu { padding: 12px 0; flex: 1; }
.sidebar-menu { padding: 20px 0; } .menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item { .menu-item {
display: block; display: flex;
padding: 15px 25px; align-items: center;
color: white; gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none; text-decoration: none;
transition: all 0.3s; transition: all 0.2s;
border-left: 4px solid transparent; border-left: 3px solid transparent;
font-size: 0.9rem;
} }
.menu-item:hover, .menu-item.active { .menu-item i { width: 18px; font-size: 0.95rem; flex-shrink: 0; text-align: center; }
background: rgba(255,255,255,0.2);
.menu-item:hover {
background: rgba(255,255,255,0.1);
color: white; color: white;
text-decoration: none; text-decoration: none;
border-left-color: rgba(255,255,255,0.5);
}
.menu-item.active {
background: rgba(255,255,255,0.15);
color: white;
border-left-color: #fff; border-left-color: #fff;
transform: translateX(5px); font-weight: 600;
} }
.menu-item i { width: 20px; margin-right: 10px; } .sidebar-footer {
padding: 16px 20px;
border-top: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
/* Main Content */ .btn-logout {
width: 100%;
background: rgba(255,255,255,0.12);
color: white;
border: 1.5px solid rgba(255,255,255,0.3);
padding: 9px 16px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.5);
color: white;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content { .main-content {
margin-left: 280px; margin-left: var(--sidebar-width);
flex: 1;
min-height: 100vh; min-height: 100vh;
display: flex;
flex-direction: column;
} }
.admin-header { /* ===== TOPBAR ===== */
.topbar {
background: white; background: white;
padding: 20px 30px; height: 64px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 0 24px;
border-bottom: 1px solid #dee2e6; display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 0 #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
} }
.content-area { padding: 30px; } .topbar-left { display: flex; align-items: center; gap: 14px; }
.page-header { .hamburger-btn {
background: white; display: none;
padding: 30px; background: none;
border-radius: 12px; border: none;
box-shadow: 0 4px 6px rgba(0,0,0,0.05); color: #374151;
margin-bottom: 30px; font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
} }
.breadcrumb { background: transparent; padding: 0; margin-bottom: 10px; } .hamburger-btn:hover { background: #f3f4f6; }
.breadcrumb-item a { color: #28a745; text-decoration: none; }
/* Stats Cards */ .topbar-breadcrumb { font-size: 0.8rem; color: #6b7280; margin-bottom: 2px; }
.topbar-breadcrumb a { color: var(--green-main); text-decoration: none; }
.topbar-title h2 { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.topbar-title p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.btn-back-website {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
border: none;
padding: 9px 18px;
border-radius: 8px;
font-weight: 600;
font-size: 0.825rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.2s;
white-space: nowrap;
}
.btn-back-website:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220,38,38,0.35);
color: white;
text-decoration: none;
}
/* ===== CONTENT AREA ===== */
.content-area { padding: 20px 24px 24px; flex: 1; }
/* ===== STATS GRID ===== */
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 20px; gap: 16px;
margin-bottom: 30px; margin-bottom: 20px;
} }
.stat-card { .stat-card {
background: white; background: white;
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 18px 16px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05); box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-left: 4px solid; border-left: 4px solid;
} }
.stat-card.primary { border-left-color: #007bff; } .stat-card.primary { border-left-color: #2563eb; }
.stat-card.warning { border-left-color: #ffc107; } .stat-card.warning { border-left-color: #d97706; }
.stat-card.info { border-left-color: #17a2b8; } .stat-card.info { border-left-color: #0891b2; }
.stat-card.success { border-left-color: #28a745; } .stat-card.success { border-left-color: var(--green-main); }
.stat-label { .stat-label {
font-size: 0.75rem; font-size: 0.72rem;
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
margin-bottom: 5px; letter-spacing: 0.04em;
margin-bottom: 4px;
} }
.stat-card.primary .stat-label { color: #007bff; } .stat-card.primary .stat-label { color: #2563eb; }
.stat-card.warning .stat-label { color: #ffc107; } .stat-card.warning .stat-label { color: #d97706; }
.stat-card.info .stat-label { color: #17a2b8; } .stat-card.info .stat-label { color: #0891b2; }
.stat-card.success .stat-label { color: #28a745; } .stat-card.success .stat-label { color: var(--green-main); }
.stat-value { font-size: 1.5rem; font-weight: 700; color: #333; } .stat-value { font-size: 1.5rem; font-weight: 800; color: #111827; }
.stat-icon { font-size: 2rem; color: #dee2e6; } .stat-icon { font-size: 1.75rem; color: #e5e7eb; }
/* ===== TABLE CARD ===== */
.table-card {
background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
overflow: hidden;
}
.table-card-header {
padding: 18px 20px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.section-title {
font-size: 1rem;
font-weight: 700;
color: #111827;
display: flex;
align-items: center;
gap: 8px;
}
.table-controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
/* Table */ /* Table */
.data-table { .data-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
background: white;
} }
.data-table th, .data-table td { .data-table th, .data-table td {
padding: 12px 15px; padding: 11px 14px;
text-align: left; text-align: left;
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #f3f4f6;
font-size: 0.875rem;
} }
.data-table th { .data-table th {
background: #f8f9fa; background: #f9fafb;
font-weight: 600; font-weight: 600;
color: #495057; color: #6b7280;
font-size: 0.85rem; font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
} }
.data-table tbody tr:hover { background-color: #f8f9fc; } .data-table tbody tr:hover { background-color: #f9fafb; }
/* Badges */ /* Badges */
.badge-status { .badge-status {
padding: 4px 10px; padding: 4px 10px;
border-radius: 12px; border-radius: 12px;
font-size: 0.75rem; font-size: 0.72rem;
font-weight: 600; font-weight: 600;
display: inline-block;
} }
.badge-pending { background: #fff3cd; color: #856404; } .badge-pending { background: #fef3c7; color: #92400e; }
.badge-processing { background: #d1ecf1; color: #0c5460; } .badge-processing { background: #dbeafe; color: #1e40af; }
.badge-shipped { background: #cce5ff; color: #004085; } .badge-shipped { background: #e0f2fe; color: #075985; }
.badge-delivered { background: #d4edda; color: #155724; } .badge-delivered { background: #dcfce7; color: #166534; }
.badge-cancelled { background: #f8d7da; color: #721c24; } .badge-cancelled { background: #fee2e2; color: #991b1b; }
.badge-paid { background: #d4edda; color: #155724; } .badge-paid { background: #dcfce7; color: #166534; }
.badge-failed { background: #f8d7da; color: #721c24; } .badge-failed { background: #fee2e2; color: #991b1b; }
/* Selects with badge style */
select.badge-status {
border: none;
cursor: pointer;
appearance: auto;
-webkit-appearance: auto;
}
/* Action Buttons */ /* Action Buttons */
.btn-action { .btn-action {
@ -183,7 +338,7 @@
border-radius: 6px; border-radius: 6px;
font-size: 0.8rem; font-size: 0.8rem;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.2s;
text-decoration: none; text-decoration: none;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -191,48 +346,72 @@
.btn-action:hover { .btn-action:hover {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.2); box-shadow: 0 2px 6px rgba(0,0,0,0.15);
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
} }
.btn-view { background: #17a2b8; color: white; } .btn-view { background: #0891b2; color: white; }
.btn-edit { background: #ffc107; color: #212529; } .btn-edit { background: #d97706; color: white; }
.btn-print { background: #6c757d; color: white; } .btn-print { background: #6b7280; color: white; }
/* Section Header */ /* Cancelled row */
.section-header { tr.row-cancelled { background-color: #f3f4f6 !important; opacity: 0.75; }
tr.row-cancelled td { color: #9ca3af !important; }
tr.row-cancelled strong, tr.row-cancelled .text-success { color: #9ca3af !important; }
tr.row-cancelled select, tr.row-cancelled .btn-action { pointer-events: none; opacity: 0.5; cursor: not-allowed; }
/* Pagination */
.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 #e5e7eb;
color: var(--green-main);
font-size: 0.83rem;
text-decoration: none;
background: white;
}
.pagination .page-item.active .page-link {
background: var(--green-main);
border-color: var(--green-main);
color: white;
}
.pagination .page-item.disabled .page-link { color: #d1d5db; pointer-events: none; }
.pagination .page-item .page-link:hover { background: #f0fdf4; border-color: var(--green-main); }
.table-footer {
padding: 14px 20px;
border-top: 1px solid #f3f4f6;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; gap: 12px;
padding-bottom: 15px; flex-wrap: wrap;
border-bottom: 2px solid #f8f9fa;
} }
.section-title { font-size: 1.3rem; font-weight: 600; color: #333; } /* Alert */
.alert-success-custom {
/* Back Button */ background: #f0fdf4;
.back-button { border: 1px solid #bbf7d0;
position: fixed; color: #166534;
top: 20px; border-radius: 10px;
right: 20px; padding: 12px 16px;
z-index: 1100; font-size: 0.875rem;
background: linear-gradient(45deg, #dc3545, #c82333); display: flex;
color: white; align-items: center;
border: none; gap: 8px;
padding: 12px 20px; margin-bottom: 16px;
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 { .fade-in {
@ -243,146 +422,121 @@
from { opacity: 0; transform: translateY(10px); } from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); } 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 { /* ===== RESPONSIVE ===== */
padding: 6px 12px; @media (max-width: 1100px) {
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); } .stats-grid { grid-template-columns: repeat(2, 1fr); }
} }
@media (max-width: 900px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.2); }
.main-content { margin-left: 0; }
.hamburger-btn { display: flex; align-items: center; justify-content: center; }
}
@media (max-width: 600px) {
.stats-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
.stat-card { padding: 14px 12px; }
.stat-value { font-size: 1.3rem; }
.stat-icon { font-size: 1.4rem; }
.content-area { padding: 14px 14px 20px; }
.topbar { padding: 0 14px; }
.btn-back-website span { display: none; }
.btn-back-website { padding: 9px 12px; }
.table-card-header { flex-direction: column; align-items: flex-start; }
.table-controls { width: 100%; }
.table-controls .form-select, .table-controls a { flex: 1; }
.table-footer { flex-direction: column; align-items: flex-start; gap: 10px; }
.data-table th, .data-table td { padding: 9px 10px; font-size: 0.8rem; }
}
@media (max-width: 380px) {
.stats-grid { grid-template-columns: 1fr; }
}
</style> </style>
</head> </head>
<body> <body>
<!-- Back Button -->
<a href="{{ route('home') }}" class="back-button"> <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
</a>
<div class="admin-container">
<!-- Sidebar --> <!-- Sidebar -->
<nav class="sidebar"> <nav class="sidebar" id="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<h3>🌱 Bibit Cabai</h3> <span class="brand-icon">🌱</span>
<p>Admin Dashboard</p> <h3>Bibit Cabai</h3>
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small> <p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div> </div>
<div class="sidebar-menu"> <div class="sidebar-menu">
<div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.dashboard') }}" class="menu-item"> <a href="{{ route('admin.dashboard') }}" class="menu-item">
<i class="fas fa-tachometer-alt"></i>Dashboard <i class="fas fa-tachometer-alt"></i><span>Dashboard</span>
</a> </a>
<a href="{{ route('admin.products.index') }}" class="menu-item"> <a href="{{ route('admin.products.index') }}" class="menu-item">
<i class="fas fa-seedling"></i>Kelola Produk <i class="fas fa-seedling"></i><span>Kelola Produk</span>
</a> </a>
<a href="{{ route('admin.orders') }}" class="menu-item active"> <a href="{{ route('admin.orders') }}" class="menu-item active">
<i class="fas fa-shopping-cart"></i>Pesanan <i class="fas fa-shopping-cart"></i><span>Pesanan</span>
</a> </a>
{{-- TAMBAHKAN DI SINI --}}
<a href="{{ route('admin.cancellations') }}" class="menu-item"> <a href="{{ route('admin.cancellations') }}" class="menu-item">
<i class="fas fa-times-circle"></i>Pengajuan Batal <i class="fas fa-times-circle"></i><span>Pengajuan Batal</span>
</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><span>Pengguna</span>
</a> </a>
<a href="{{ route('admin.laporan') }}" 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><span>Laporan</span>
</a> </a>
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"> </div>
<i class="fas fa-cog"></i>Pengaturan
</a> --> <div class="sidebar-footer">
<div class="mt-4 px-3">
<form method="POST" action="{{ route('admin.logout') }}"> <form method="POST" action="{{ route('admin.logout') }}">
@csrf @csrf
<button type="submit" class="btn btn-outline-light btn-sm w-100"> <button type="submit" class="btn-logout" onclick="return confirm('Apakah Anda yakin ingin logout?')">
<i class="fas fa-sign-out-alt me-2"></i>Logout <i class="fas fa-sign-out-alt"></i><span>Logout</span>
</button> </button>
</form> </form>
</div> </div>
</div>
</nav> </nav>
<!-- Main Content --> <!-- Main Content -->
<div class="main-content"> <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 active" aria-current="page">Pesanan</li>
</ol>
</nav>
<h2>Orders Management</h2>
<p class="mb-0">Kelola semua pesanan pelanggan di sini!</p>
</div>
<!-- Topbar -->
<header class="topbar">
<div class="topbar-left">
<button class="hamburger-btn" id="hamburgerBtn" onclick="toggleSidebar()" aria-label="Toggle menu">
<i class="fas fa-bars"></i>
</button>
<div class="topbar-title">
<div class="topbar-breadcrumb">
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
&nbsp;/&nbsp;Pesanan
</div>
<h2>Orders Management</h2>
</div>
</div>
<a href="{{ route('home') }}" class="btn-back-website">
<i class="fas fa-arrow-left"></i>
<span>Kembali ke Website</span>
</a>
</header>
<!-- Content -->
<div class="content-area fade-in"> <div class="content-area fade-in">
@if(session('success')) @if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert"> <div class="alert-success-custom">
<i class="fas fa-check-circle me-2"></i>{{ session('success') }} <i class="fas fa-check-circle"></i>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <span>{{ session('success') }}</span>
</div> </div>
@endif @endif
<!-- Stats Cards --> <!-- Stats Grid -->
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card primary"> <div class="stat-card primary">
<div> <div>
@ -393,36 +547,36 @@
</div> </div>
<div class="stat-card warning"> <div class="stat-card warning">
<div> <div>
<div class="stat-label">Pending Orders</div> <div class="stat-label">Pending</div>
<div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'pending')->count() }}</div> <div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'pending')->count() }}</div>
</div> </div>
<div class="stat-icon"><i class="fas fa-clock"></i></div> <div class="stat-icon"><i class="fas fa-clock"></i></div>
</div> </div>
<div class="stat-card info"> <div class="stat-card info">
<div> <div>
<div class="stat-label">Processing Orders</div> <div class="stat-label">Processing</div>
<div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'processing')->count() }}</div> <div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'processing')->count() }}</div>
</div> </div>
<div class="stat-icon"><i class="fas fa-box"></i></div> <div class="stat-icon"><i class="fas fa-box"></i></div>
</div> </div>
<div class="stat-card success"> <div class="stat-card success">
<div> <div>
<div class="stat-label">Delivered Orders</div> <div class="stat-label">Delivered</div>
<div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'delivered')->count() }}</div> <div class="stat-value">{{ \App\Models\Transaksi::where('order_status', 'delivered')->count() }}</div>
</div> </div>
<div class="stat-icon"><i class="fas fa-check-circle"></i></div> <div class="stat-icon"><i class="fas fa-check-circle"></i></div>
</div> </div>
</div> </div>
<!-- Orders Table --> <!-- Table Card -->
<div class="page-header"> <div class="table-card">
<div class="section-header"> <div class="table-card-header">
<h3 class="section-title"> <div class="section-title">
<i class="fas fa-shopping-cart text-success me-2"></i> <i class="fas fa-shopping-cart text-success"></i>
Daftar Pesanan ({{ $orders->total() }}) Daftar Pesanan ({{ $orders->total() }})
</h3> </div>
<div class="d-flex gap-2 align-items-center"> <div class="table-controls">
<select class="form-select form-select-sm" id="filterStatus" onchange="filterOrders()" style="width: 160px;"> <select class="form-select form-select-sm" id="filterStatus" onchange="filterOrders()" style="min-width:140px;">
<option value="">Semua Status</option> <option value="">Semua Status</option>
<option value="pending">Pending</option> <option value="pending">Pending</option>
<option value="processing">Processing</option> <option value="processing">Processing</option>
@ -440,14 +594,14 @@
<table class="data-table" id="ordersTable"> <table class="data-table" id="ordersTable">
<thead> <thead>
<tr> <tr>
<th>Invoice Number</th> <th>Invoice</th>
<th>Customer</th> <th>Customer</th>
<th>Phone</th> <th>Phone</th>
<th>Order Status</th> <th>Order Status</th>
<th>Payment Status</th> <th>Payment Status</th>
<th>Metode Pembayaran</th> <th>Metode</th>
<th>Total</th> <th>Total</th>
<th>Order Date</th> <th>Tanggal</th>
<th>Aksi</th> <th>Aksi</th>
</tr> </tr>
</thead> </thead>
@ -459,8 +613,7 @@ class="{{ $order->order_status === 'cancelled' ? 'row-cancelled' : '' }}">
<td>{{ $order->customer_name }}</td> <td>{{ $order->customer_name }}</td>
<td>{{ $order->customer_phone }}</td> <td>{{ $order->customer_phone }}</td>
<td> <td>
<select class="badge-status badge-{{ $order->order_status }} border-0 cursor-pointer" <select class="badge-status badge-{{ $order->order_status }} border-0"
style="cursor:pointer; appearance:auto;"
onchange="updateStatus({{ $order->id }}, 'order_status', this.value, this)"> onchange="updateStatus({{ $order->id }}, 'order_status', this.value, this)">
<option value="pending" {{ $order->order_status == 'pending' ? 'selected' : '' }}>Pending</option> <option value="pending" {{ $order->order_status == 'pending' ? 'selected' : '' }}>Pending</option>
<option value="processing" {{ $order->order_status == 'processing' ? 'selected' : '' }}>Processing</option> <option value="processing" {{ $order->order_status == 'processing' ? 'selected' : '' }}>Processing</option>
@ -469,14 +622,11 @@ class="{{ $order->order_status === 'cancelled' ? 'row-cancelled' : '' }}">
<option value="cancelled" {{ $order->order_status == 'cancelled' ? 'selected' : '' }}>Cancelled</option> <option value="cancelled" {{ $order->order_status == 'cancelled' ? 'selected' : '' }}>Cancelled</option>
</select> </select>
@if($order->cancellation && $order->cancellation->status === 'pending') @if($order->cancellation && $order->cancellation->status === 'pending')
<br><span class="badge-status badge-warning mt-1" style="font-size:0.7rem;"> <br><span class="badge-status badge-warning mt-1" style="font-size:0.68rem;"> Minta Batal</span>
Minta Batal
</span>
@endif @endif
</td> </td>
<td> <td>
<select class="badge-status badge-{{ $order->payment_status }} border-0" <select class="badge-status badge-{{ $order->payment_status }} border-0"
style="cursor:pointer; appearance:auto;"
onchange="updateStatus({{ $order->id }}, 'payment_status', this.value, this)"> onchange="updateStatus({{ $order->id }}, 'payment_status', this.value, this)">
<option value="pending" {{ $order->payment_status == 'pending' ? 'selected' : '' }}>Pending</option> <option value="pending" {{ $order->payment_status == 'pending' ? 'selected' : '' }}>Pending</option>
<option value="paid" {{ $order->payment_status == 'paid' ? 'selected' : '' }}>Paid</option> <option value="paid" {{ $order->payment_status == 'paid' ? 'selected' : '' }}>Paid</option>
@ -484,38 +634,40 @@ class="{{ $order->order_status === 'cancelled' ? 'row-cancelled' : '' }}">
</select> </select>
</td> </td>
<td> <td>
<span class="badge-status" style="background:#ced4da;color:#212529;"> <span class="badge-status" style="background:#f3f4f6;color:#374151;">
{{ ucwords(str_replace('_', ' ', $order->payment_method ?? '-')) }} {{ ucwords(str_replace('_', ' ', $order->payment_method ?? '-')) }}
</span> </span>
</td> </td>
<td><strong class="text-success">Rp {{ number_format($order->total_amount, 0, ',', '.') }}</strong></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><small style="color:#6b7280;">{{ $order->created_at->format('d M Y H:i') }}</small></td>
<td> <td>
<div class="d-flex gap-1"> <div class="d-flex gap-1 flex-nowrap">
<a href="{{ route('admin.transaksis.show', $order->id) }}" <a href="{{ route('admin.transaksis.show', $order->id) }}" class="btn-action btn-view" title="Lihat Detail">
class="btn-action btn-view" title="View Details">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</a> </a>
<a href="{{ route('admin.transaksis.edit', $order->id) }}" <a href="{{ route('admin.transaksis.edit', $order->id) }}" class="btn-action btn-edit" title="Edit">
class="btn-action btn-edit" title="Edit">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</a> </a>
<a href="{{ route('admin.transaksis.print', $order->id) }}" <a href="{{ route('admin.transaksis.print', $order->id) }}" class="btn-action btn-print" title="Print Invoice" target="_blank">
class="btn-action btn-print" title="Print Invoice" target="_blank">
<i class="fas fa-print"></i> <i class="fas fa-print"></i>
</a> </a>
</div> </div>
</td> </td>
</tr> </tr>
@empty @empty
<tr>
<td colspan="9" style="text-align:center; padding:40px; color:#9ca3af;">
<i class="fas fa-inbox" style="font-size:2rem; display:block; margin-bottom:10px; opacity:0.3;"></i>
Belum ada pesanan
</td>
</tr>
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination --> <div class="table-footer">
<div class="d-flex justify-content-between align-items-center mt-4"> <small style="color:#6b7280;">
<small class="text-muted">
Menampilkan {{ $orders->firstItem() ?? 0 }} sampai {{ $orders->lastItem() ?? 0 }} Menampilkan {{ $orders->firstItem() ?? 0 }} sampai {{ $orders->lastItem() ?? 0 }}
dari {{ $orders->total() }} pesanan dari {{ $orders->total() }} pesanan
</small> </small>
@ -525,11 +677,32 @@ class="btn-action btn-print" title="Print Invoice" target="_blank">
</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>
<script>
// Sidebar toggle
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.toggle('open');
overlay.classList.toggle('show');
document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
}
function closeSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.remove('open');
overlay.classList.remove('show');
document.body.style.overflow = '';
}
window.addEventListener('resize', function() {
if (window.innerWidth > 900) closeSidebar();
});
// Filter orders
function filterOrders() { function filterOrders() {
const filterValue = document.getElementById('filterStatus').value.toLowerCase(); const filterValue = document.getElementById('filterStatus').value.toLowerCase();
const rows = document.querySelectorAll('#ordersTable tbody tr[data-status]'); const rows = document.querySelectorAll('#ordersTable tbody tr[data-status]');
@ -539,8 +712,8 @@ function filterOrders() {
}); });
} }
// Update status via AJAX
function updateStatus(orderId, field, value, selectEl) { function updateStatus(orderId, field, value, selectEl) {
// Jika status sudah cancelled, tidak bisa diubah
if (field === 'order_status') { if (field === 'order_status') {
const row = selectEl.closest('tr'); const row = selectEl.closest('tr');
const currentStatus = row.getAttribute('data-status'); const currentStatus = row.getAttribute('data-status');
@ -548,7 +721,7 @@ function updateStatus(orderId, field, value, selectEl) {
alert('Pesanan yang sudah selesai atau dibatalkan tidak dapat diubah kembali.'); alert('Pesanan yang sudah selesai atau dibatalkan tidak dapat diubah kembali.');
selectEl.value = currentStatus; selectEl.value = currentStatus;
return; return;
} }
} }
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
@ -564,17 +737,13 @@ function updateStatus(orderId, field, value, selectEl) {
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.success) { 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']; const badgeClasses = ['badge-pending','badge-processing','badge-shipped','badge-delivered','badge-cancelled','badge-paid','badge-failed'];
selectEl.classList.remove(...badgeClasses); selectEl.classList.remove(...badgeClasses);
selectEl.classList.add('badge-' + value); selectEl.classList.add('badge-' + value);
// Update data-status di row jika yang diubah adalah order_status
if (field === 'order_status') { if (field === 'order_status') {
const row = selectEl.closest('tr'); const row = selectEl.closest('tr');
row.setAttribute('data-status', value); row.setAttribute('data-status', value);
// Tambahkan/hapus class abu-abu jika status cancelled
if (value === 'cancelled') { if (value === 'cancelled') {
row.classList.add('row-cancelled'); row.classList.add('row-cancelled');
} else { } else {
@ -582,7 +751,6 @@ function updateStatus(orderId, field, value, selectEl) {
} }
} }
// Tampilkan notifikasi kecil
showToast('Status berhasil diupdate!'); showToast('Status berhasil diupdate!');
} else { } else {
alert('Gagal update: ' + data.message); alert('Gagal update: ' + data.message);
@ -594,10 +762,10 @@ function updateStatus(orderId, field, value, selectEl) {
function showToast(msg) { function showToast(msg) {
const toast = document.createElement('div'); const toast = document.createElement('div');
toast.innerText = msg; 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;'; toast.style.cssText = 'position:fixed;bottom:24px;right:24px;background:#16a34a;color:white;padding:10px 20px;border-radius:8px;z-index:9999;font-size:14px;font-weight:500;box-shadow:0 4px 12px rgba(0,0,0,0.15);';
document.body.appendChild(toast); document.body.appendChild(toast);
setTimeout(() => toast.remove(), 2500); setTimeout(() => toast.remove(), 2500);
} }
</script> </script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@ -1,238 +1,814 @@
@extends('admin.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 Produk - Bibit Cabai</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
@section('title', 'Edit Produk') <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">
@section('content') <style>
<!-- Back Button --> * { margin: 0; padding: 0; box-sizing: border-box; }
<a href="{{ route('admin.products.index') }}" class="back-button">
<i class="fas fa-arrow-left me-2"></i>Kembali ke Daftar Produk :root {
</a> --sidebar-width: 260px;
<!-- Main Content --> --green-dark: #15803d;
<div class="main-content"> --green-main: #16a34a;
<div class="admin-header"> }
<nav aria-label="breadcrumb">
<ol class="breadcrumb"> body {
<li class="breadcrumb-item"> font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
<a href="{{ route('admin.dashboard') }}"> background: #f1f5f2;
<i class="fas fa-home"></i> Dashboard overflow-x: hidden;
</a> }
</li>
<li class="breadcrumb-item"> /* ===== SIDEBAR ===== */
<a href="{{ route('admin.products.index') }}">Kelola Produk</a> .sidebar {
</li> width: var(--sidebar-width);
<li class="breadcrumb-item active" aria-current="page">Edit Produk</li> background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
</ol> color: white;
</nav> min-height: 100vh;
<h2>Edit Produk</h2> position: fixed;
<p class="mb-0">Edit informasi produk {{ $product->name }}</p> top: 0; left: 0;
z-index: 1050;
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.sidebar-header {
padding: 24px 20px 20px;
text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
.sidebar-header .brand-icon { font-size: 2rem; margin-bottom: 6px; display: block; }
.sidebar-header h3 { font-size: 1.35rem; font-weight: 700; margin-bottom: 2px; }
.sidebar-header .subtitle { opacity: 0.65; font-size: 0.78rem; margin-bottom: 6px; }
.sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.sidebar-menu { padding: 12px 0; flex: 1; }
.menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none;
transition: all 0.2s;
border-left: 3px solid transparent;
font-size: 0.9rem;
}
.menu-item i { width: 18px; font-size: 0.95rem; flex-shrink: 0; text-align: center; }
.menu-item:hover {
background: rgba(255,255,255,0.1);
color: white;
text-decoration: none;
border-left-color: rgba(255,255,255,0.5);
}
.menu-item.active {
background: rgba(255,255,255,0.15);
color: white;
border-left-color: #fff;
font-weight: 600;
}
.sidebar-footer {
padding: 16px 20px;
border-top: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
.btn-logout {
width: 100%;
background: rgba(255,255,255,0.12);
color: white;
border: 1.5px solid rgba(255,255,255,0.3);
padding: 9px 16px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.5);
color: white;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content {
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== TOPBAR ===== */
.topbar {
background: white;
height: 64px;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 0 #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.topbar-left { display: flex; align-items: center; gap: 14px; }
.hamburger-btn {
display: none;
background: none;
border: none;
color: #374151;
font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
.hamburger-btn:hover { background: #f3f4f6; }
.topbar-breadcrumb { font-size: 0.8rem; color: #6b7280; margin-bottom: 2px; }
.topbar-breadcrumb a { color: var(--green-main); text-decoration: none; }
.topbar-breadcrumb a:hover { text-decoration: underline; }
.topbar-title h2 { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.topbar-title p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.btn-back-header {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
border: none;
padding: 9px 18px;
border-radius: 8px;
font-weight: 600;
font-size: 0.825rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.2s;
white-space: nowrap;
}
.btn-back-header:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220,38,38,0.35);
color: white;
text-decoration: none;
}
/* ===== CONTENT AREA ===== */
.content-area { padding: 20px 24px 32px; flex: 1; }
/* ===== FORM CARD ===== */
.form-card {
background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
overflow: hidden;
}
.form-card-header {
padding: 20px 24px 16px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
gap: 10px;
}
.form-card-header-icon {
width: 38px; height: 38px;
background: #fef9c3;
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #ca8a04;
font-size: 1rem;
flex-shrink: 0;
}
.form-card-header h3 { font-size: 1rem; font-weight: 700; color: #111827; margin: 0; }
.form-card-header p { font-size: 0.78rem; color: #6b7280; margin: 0; }
.form-card-body { padding: 24px; }
/* ===== FORM ELEMENTS ===== */
.form-label {
font-size: 0.82rem;
font-weight: 600;
color: #374151;
margin-bottom: 6px;
}
.req { color: #dc2626; }
.form-control, .form-select {
border: 1.5px solid #e5e7eb;
border-radius: 8px;
font-size: 0.875rem;
padding: 9px 12px;
color: #111827;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-control:focus, .form-select:focus {
border-color: var(--green-main);
box-shadow: 0 0 0 3px rgba(22,163,74,0.12);
outline: none;
}
.form-control.is-invalid, .form-select.is-invalid { border-color: #dc2626; }
.input-group-text {
background: #f9fafb;
border: 1.5px solid #e5e7eb;
border-radius: 8px 0 0 8px;
color: #6b7280;
font-size: 0.875rem;
}
.input-group .form-control,
.input-group .form-select { border-radius: 0 8px 8px 0; }
.form-text { font-size: 0.75rem; color: #9ca3af; margin-top: 4px; }
textarea.form-control { resize: vertical; min-height: 100px; }
/* ===== CURRENT IMAGE ===== */
.current-image-box {
border: 1.5px solid #e5e7eb;
border-radius: 10px;
padding: 14px;
background: #f9fafb;
display: flex;
align-items: center;
gap: 14px;
}
.current-image-box img {
width: 80px; height: 80px;
object-fit: cover;
border-radius: 8px;
border: 1px solid #e5e7eb;
flex-shrink: 0;
}
.current-image-info { font-size: 0.8rem; color: #6b7280; }
.current-image-info strong { display: block; color: #374151; margin-bottom: 3px; font-size: 0.82rem; }
/* ===== IMAGE UPLOAD ===== */
.image-upload-area {
border: 2px dashed #d1d5db;
border-radius: 10px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
background: #fafafa;
position: relative;
}
.image-upload-area:hover { border-color: var(--green-main); background: #f0fdf4; }
.image-upload-area input[type=file] {
position: absolute; inset: 0; width: 100%; height: 100%;
opacity: 0; cursor: pointer;
}
.upload-icon { font-size: 2rem; color: #d1d5db; margin-bottom: 8px; }
.upload-text { font-size: 0.82rem; color: #6b7280; }
.upload-hint { font-size: 0.72rem; color: #9ca3af; margin-top: 4px; }
.image-preview-box { display: none; margin-top: 14px; }
.image-preview-box img {
max-width: 150px; max-height: 150px;
border-radius: 10px; object-fit: cover;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* ===== INFO BANNER ===== */
.info-banner {
background: #fffbeb;
border: 1px solid #fde68a;
border-radius: 10px;
padding: 12px 16px;
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 0.82rem;
color: #92400e;
margin-bottom: 20px;
}
.info-banner i { margin-top: 1px; flex-shrink: 0; }
/* ===== SECTION DIVIDER ===== */
.section-divider {
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #9ca3af;
padding-bottom: 10px;
border-bottom: 1px solid #f3f4f6;
margin-bottom: 18px;
}
/* ===== ACTION BUTTONS ===== */
.form-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
flex-wrap: wrap;
padding-top: 20px;
border-top: 1px solid #f3f4f6;
margin-top: 24px;
}
.btn-cancel {
background: #f9fafb;
color: #374151;
border: 1.5px solid #e5e7eb;
border-radius: 8px;
padding: 10px 20px;
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
display: inline-flex; align-items: center; gap: 7px;
transition: all 0.2s;
}
.btn-cancel:hover { background: #f3f4f6; color: #111827; text-decoration: none; }
.btn-reset-form {
background: #fff7ed;
color: #c2410c;
border: 1.5px solid #fed7aa;
border-radius: 8px;
padding: 10px 20px;
font-size: 0.875rem;
font-weight: 600;
display: inline-flex; align-items: center; gap: 7px;
transition: all 0.2s;
cursor: pointer;
}
.btn-reset-form:hover { background: #ffedd5; }
.btn-save {
background: linear-gradient(135deg, #ca8a04, #a16207);
color: white;
border: none;
border-radius: 8px;
padding: 10px 24px;
font-size: 0.875rem;
font-weight: 700;
display: inline-flex; align-items: center; gap: 7px;
transition: all 0.2s;
cursor: pointer;
}
.btn-save:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(202,138,4,0.35);
color: white;
}
.fade-in { animation: fadeIn 0.3s ease; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ===== RESPONSIVE ===== */
@media (max-width: 900px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.2); }
.main-content { margin-left: 0; }
.hamburger-btn { display: flex; align-items: center; justify-content: center; }
}
@media (max-width: 767px) {
.content-area { padding: 14px 14px 24px; }
.topbar { padding: 0 14px; height: 58px; }
.btn-back-header span { display: none; }
.btn-back-header { padding: 9px 12px; }
.form-card-body { padding: 16px; }
.form-actions { justify-content: stretch; }
.form-actions > * { flex: 1; justify-content: center; }
}
</style>
</head>
<body>
<div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<!-- ===== SIDEBAR ===== -->
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<span class="brand-icon">🌱</span>
<h3>Bibit Cabai</h3>
<p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div> </div>
<div class="sidebar-menu">
<div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.dashboard') }}" class="menu-item">
<i class="fas fa-tachometer-alt"></i><span>Dashboard</span>
</a>
<a href="{{ route('admin.products.index') }}" class="menu-item active">
<i class="fas fa-seedling"></i><span>Kelola Produk</span>
</a>
<a href="{{ route('admin.orders') }}" class="menu-item">
<i class="fas fa-shopping-cart"></i><span>Pesanan</span>
</a>
<a href="{{ route('admin.cancellations') }}" class="menu-item">
<i class="fas fa-times-circle"></i><span>Pengajuan Batal</span>
</a>
<a href="{{ route('admin.users') }}" class="menu-item">
<i class="fas fa-users"></i><span>Pengguna</span>
</a>
<a href="{{ route('admin.laporan') }}" class="menu-item">
<i class="fas fa-chart-line"></i><span>Laporan</span>
</a>
</div>
<div class="sidebar-footer">
<form method="POST" action="{{ route('admin.logout') }}">
@csrf
<button type="submit" class="btn-logout">
<i class="fas fa-sign-out-alt"></i><span>Logout</span>
</button>
</form>
</div>
</nav>
<!-- ===== MAIN ===== -->
<div class="main-content">
<!-- Topbar -->
<header class="topbar">
<div class="topbar-left">
<button class="hamburger-btn" onclick="toggleSidebar()">
<i class="fas fa-bars"></i>
</button>
<div class="topbar-title">
<div class="topbar-breadcrumb">
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
&nbsp;/&nbsp;<a href="{{ route('admin.products.index') }}">Kelola Produk</a>
&nbsp;/&nbsp;Edit Produk
</div>
<h2>Edit Produk</h2>
<p>Edit informasi produk {{ $product->name }}</p>
</div>
</div>
<a href="{{ route('admin.products.index') }}" class="btn-back-header">
<i class="fas fa-arrow-left"></i>
<span>Kembali ke Produk</span>
</a>
</header>
<!-- Content -->
<div class="content-area fade-in"> <div class="content-area fade-in">
<!-- Display Validation Errors -->
@if ($errors->any()) @if ($errors->any())
<div class="alert alert-danger"> <div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
<ul class="mb-0"> <i class="fas fa-exclamation-circle me-2"></i>
<strong>Terdapat kesalahan:</strong>
<ul class="mb-0 mt-1">
@foreach ($errors->all() as $error) @foreach ($errors->all() as $error)
<li>{{ $error }}</li> <li>{{ $error }}</li>
@endforeach @endforeach
</ul> </ul>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div> </div>
@endif @endif
<div class="card"> @if (session('success'))
<div class="card-header"> <div class="alert alert-success alert-dismissible fade show mb-4" role="alert">
<h4 class="mb-0"> <i class="fas fa-check-circle me-2"></i>{{ session('success') }}
<i class="fas fa-edit text-warning me-2"></i> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
Form Edit Produk
</h4>
</div> </div>
<div class="card-body"> @endif
<form action="{{ route('admin.products.update', $product) }}" method="POST" enctype="multipart/form-data">
<div class="form-card">
<div class="form-card-header">
<div class="form-card-header-icon">
<i class="fas fa-edit"></i>
</div>
<div>
<h3>Form Edit Produk</h3>
<p>Ubah data produk sesuai kebutuhan</p>
</div>
</div>
<div class="form-card-body">
<form action="{{ route('admin.products.update', $product) }}" method="POST" enctype="multipart/form-data" id="productForm">
@csrf @csrf
@method('PUT') @method('PUT')
<div class="row"> <!-- INFORMASI DASAR -->
<!-- Nama Produk --> <div class="section-divider">Informasi Dasar</div>
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Nama Produk <span class="text-danger">*</span></label> <div class="row g-3 mb-3">
<input type="text" class="form-control @error('name') is-invalid @enderror" <div class="col-md-6">
id="name" name="name" value="{{ old('name', $product->name) }}" required> <label class="form-label">Nama Produk <span class="req">*</span></label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-seedling"></i></span>
<input type="text"
class="form-control @error('name') is-invalid @enderror"
name="name" id="name"
value="{{ old('name', $product->name) }}"
placeholder="Contoh: Bibit Cabai Rawit Genni"
required>
</div>
@error('name') @error('name')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@enderror @enderror
</div> </div>
<!-- Kategori --> <div class="col-md-6">
<div class="col-md-6 mb-3"> <label class="form-label">Kategori <span class="req">*</span></label>
<label for="category" class="form-label">Kategori <span class="text-danger">*</span></label> <div class="input-group">
<select class="form-select @error('category') is-invalid @enderror" id="category" name="category" required> <span class="input-group-text"><i class="fas fa-list"></i></span>
<select class="form-select @error('category') is-invalid @enderror"
name="category" id="category" required>
<option value="">Pilih Kategori</option> <option value="">Pilih Kategori</option>
<option value="Sayuran" {{ old('category', $product->category) === 'Sayuran' ? 'selected' : '' }}>Sayuran</option> <option value="Sayuran" {{ old('category', $product->category) === 'Sayuran' ? 'selected' : '' }}>Sayuran</option>
<option value="Buah" {{ old('category', $product->category) === 'Buah' ? 'selected' : '' }}>Buah</option> <option value="Buah" {{ old('category', $product->category) === 'Buah' ? 'selected' : '' }}>Buah</option>
<option value="Herbal" {{ old('category', $product->category) === 'Herbal' ? 'selected' : '' }}>Herbal</option> <option value="Herbal" {{ old('category', $product->category) === 'Herbal' ? 'selected' : '' }}>Herbal</option>
<option value="Hias" {{ old('category', $product->category) === 'Hias' ? 'selected' : '' }}>Hias</option> <option value="Hias" {{ old('category', $product->category) === 'Hias' ? 'selected' : '' }}>Hias</option>
</select> </select>
</div>
@error('category') @error('category')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@enderror @enderror
</div> </div>
</div>
<!-- Harga --> <!-- HARGA, STOK, STATUS, LABEL -->
<div class="col-md-6 mb-3"> <div class="row g-3 mb-3">
<label for="price" class="form-label">Harga <span class="text-danger">*</span></label> <div class="col-sm-6 col-md-3">
<label class="form-label">Harga <span class="req">*</span></label>
<div class="input-group"> <div class="input-group">
<span class="input-group-text">Rp</span> <span class="input-group-text">Rp</span>
<input type="number" class="form-control @error('price') is-invalid @enderror" <input type="number"
id="price" name="price" value="{{ old('price', $product->price) }}" class="form-control @error('price') is-invalid @enderror"
min="0" step="0.01" required> name="price" id="price"
value="{{ old('price', $product->price) }}"
min="0" step="1000" placeholder="0" required>
</div> </div>
@error('price') @error('price')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@enderror @enderror
</div> </div>
<!-- Stok --> <div class="col-sm-6 col-md-3">
<div class="col-md-6 mb-3"> <label class="form-label">Stok <span class="req">*</span></label>
<label for="stock" class="form-label">Stok <span class="text-danger">*</span></label> <div class="input-group">
<input type="number" class="form-control @error('stock') is-invalid @enderror" <span class="input-group-text"><i class="fas fa-boxes"></i></span>
id="stock" name="stock" value="{{ old('stock', $product->stock) }}" <input type="number"
min="0" required> class="form-control @error('stock') is-invalid @enderror"
name="stock" id="stock"
value="{{ old('stock', $product->stock) }}"
min="0" placeholder="0" required>
</div>
@error('stock') @error('stock')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@enderror @enderror
</div> </div>
<!-- Status --> <div class="col-sm-6 col-md-3">
<div class="col-md-6 mb-3"> <label class="form-label">Status <span class="req">*</span></label>
<label for="status" class="form-label">Status <span class="text-danger">*</span></label> <div class="input-group">
<select class="form-select @error('status') is-invalid @enderror" id="status" name="status" required> <span class="input-group-text"><i class="fas fa-toggle-on"></i></span>
<select class="form-select @error('status') is-invalid @enderror"
name="status" id="status" required>
<option value="aktif" {{ old('status', $product->status) === 'aktif' ? 'selected' : '' }}>Aktif</option> <option value="aktif" {{ old('status', $product->status) === 'aktif' ? 'selected' : '' }}>Aktif</option>
<option value="nonaktif" {{ old('status', $product->status) === 'nonaktif' ? 'selected' : '' }}>Non-aktif</option> <option value="nonaktif" {{ old('status', $product->status) === 'nonaktif' ? 'selected' : '' }}>Non-aktif</option>
</select> </select>
</div>
@error('status') @error('status')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@enderror @enderror
</div> </div>
<!-- Label --> <div class="col-sm-6 col-md-3">
<div class="col-md-6 mb-3"> <label class="form-label">Label</label>
<label for="label" class="form-label">Label</label> <div class="input-group">
<select class="form-select @error('label') is-invalid @enderror" id="label" name="label"> <span class="input-group-text"><i class="fas fa-tag"></i></span>
<option value="">Tidak Ada Label</option> <select class="form-select @error('label') is-invalid @enderror"
name="label" id="label">
<option value="">Tidak Ada</option>
<option value="baru" {{ old('label', $product->label) === 'baru' ? 'selected' : '' }}>Baru</option> <option value="baru" {{ old('label', $product->label) === 'baru' ? 'selected' : '' }}>Baru</option>
<option value="populer" {{ old('label', $product->label) === 'populer' ? 'selected' : '' }}>Populer</option> <option value="populer" {{ old('label', $product->label) === 'populer' ? 'selected' : '' }}>Populer</option>
<option value="diskon" {{ old('label', $product->label) === 'diskon' ? 'selected' : '' }}>Diskon</option> <option value="diskon" {{ old('label', $product->label) === 'diskon' ? 'selected' : '' }}>Diskon</option>
<option value="terbatas" {{ old('label', $product->label) === 'terbatas' ? 'selected' : '' }}>Terbatas</option> <option value="terbatas" {{ old('label', $product->label) === 'terbatas' ? 'selected' : '' }}>Terbatas</option>
</select> </select>
</div>
@error('label') @error('label')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@enderror @enderror
</div> </div>
<!-- Deskripsi -->
<div class="col-12 mb-3">
<label for="description" class="form-label">Deskripsi <span class="text-danger">*</span></label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description" name="description" rows="4" required>{{ old('description', $product->description) }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div> </div>
<!-- Gambar Saat Ini --> <!-- DESKRIPSI -->
@if($product->image_url)
<div class="col-12 mb-3">
<label class="form-label">Gambar Saat Ini</label>
<div class="mb-3"> <div class="mb-3">
<img src="{{ $product->image_url }}" alt="{{ $product->name }}" <label class="form-label">Deskripsi Produk <span class="req">*</span></label>
class="img-thumbnail" style="max-width: 200px;"> <textarea class="form-control @error('description') is-invalid @enderror"
name="description" id="description" rows="4"
placeholder="Jelaskan keunggulan dan cara perawatan bibit..." required>{{ old('description', $product->description) }}</textarea>
<div class="form-text">Deskripsi yang detail akan membantu pelanggan memahami produk Anda.</div>
@error('description')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<!-- GAMBAR -->
<div class="section-divider" style="margin-top:24px">Gambar Produk</div>
@if($product->image_url)
<div class="mb-3">
<label class="form-label">Gambar Saat Ini</label>
<div class="current-image-box">
<img src="{{ $product->image_url }}" alt="{{ $product->name }}">
<div class="current-image-info">
<strong>{{ $product->name }}</strong>
Gambar produk yang sedang aktif. Upload gambar baru di bawah untuk menggantinya.
</div>
</div> </div>
</div> </div>
@endif @endif
<!-- Upload Gambar Baru --> <div class="mb-3">
<div class="col-12 mb-3"> <label class="form-label">
<label for="image" class="form-label">Gambar Produk (Opsional)</label> Upload Gambar Baru
<input type="file" class="form-control @error('image') is-invalid @enderror" <span style="font-weight:400;color:#9ca3af">(Opsional kosongkan jika tidak ingin mengubah)</span>
id="image" name="image" accept="image/*"> </label>
<div class="form-text">Format: JPG, PNG, JPEG. Maksimal 2MB. Kosongkan jika tidak ingin mengubah gambar.</div> <div class="image-upload-area" id="uploadArea">
<input type="file" name="image" id="image" accept="image/*"
onchange="previewImage(this)">
<div id="uploadPlaceholder">
<div class="upload-icon"><i class="fas fa-cloud-upload-alt"></i></div>
<div class="upload-text">Klik atau seret gambar ke sini</div>
<div class="upload-hint">JPG, PNG, GIF Maks. 2MB Disarankan 500×500px</div>
</div>
</div>
@error('image') @error('image')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@enderror @enderror
<div class="image-preview-box" id="imagePreview">
<img id="previewImg" src="" alt="Preview">
<div class="form-text mt-1">
<span id="previewName"></span>
<a href="#" onclick="clearImage(); return false;" style="color:#dc2626;margin-left:8px">
<i class="fas fa-times"></i> Hapus
</a>
</div>
</div> </div>
</div> </div>
<!-- Action Buttons --> <!-- INFO BANNER -->
<div class="d-flex justify-content-between mt-4"> <div class="info-banner">
<a href="{{ route('admin.products.index') }}" class="btn btn-secondary"> <i class="fas fa-exclamation-triangle"></i>
<i class="fas fa-arrow-left me-2"></i>Kembali <div>Pastikan semua perubahan sudah benar sebelum menyimpan. Perubahan akan langsung tampil di website.</div>
</a>
<div>
<button type="reset" class="btn btn-outline-warning me-2">
<i class="fas fa-undo me-2"></i>Reset
</button>
<button type="submit" class="btn btn-success">
<i class="fas fa-save me-2"></i>Update Produk
</button>
</div> </div>
<!-- TOMBOL AKSI -->
<div class="form-actions">
<a href="{{ route('admin.products.index') }}" class="btn-cancel">
<i class="fas fa-times"></i> Batal
</a>
<button type="reset" class="btn-reset-form" onclick="clearImage()">
<i class="fas fa-undo"></i> Reset
</button>
<button type="submit" class="btn-save">
<i class="fas fa-save"></i> Update Produk
</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// ===== SIDEBAR =====
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
document.getElementById('sidebarOverlay').classList.toggle('show');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('sidebarOverlay').classList.remove('show');
}
window.addEventListener('resize', function() {
if (window.innerWidth > 900) closeSidebar();
});
<style> // ===== IMAGE PREVIEW =====
.back-button { function previewImage(input) {
position: fixed; if (input.files && input.files[0]) {
top: 20px; if (input.files[0].size > 2 * 1024 * 1024) {
right: 20px; alert('Ukuran file terlalu besar! Maksimal 2MB.');
z-index: 1100; input.value = '';
background: linear-gradient(45deg, #ffc107, #e0a800); return;
color: #212529; }
border: none; const reader = new FileReader();
padding: 12px 20px; reader.onload = function(e) {
border-radius: 25px; document.getElementById('previewImg').src = e.target.result;
font-weight: 600; document.getElementById('previewName').textContent = input.files[0].name;
transition: all 0.3s ease; document.getElementById('imagePreview').style.display = 'block';
text-decoration: none; document.getElementById('uploadPlaceholder').style.opacity = '0.4';
} };
reader.readAsDataURL(input.files[0]);
}
}
.back-button:hover { function clearImage() {
background: linear-gradient(45deg, #e0a800, #c69500); document.getElementById('image').value = '';
transform: translateY(-2px); document.getElementById('imagePreview').style.display = 'none';
color: #212529; document.getElementById('uploadPlaceholder').style.opacity = '1';
text-decoration: none; }
}
.card { // ===== PRICE: digits only =====
border: none; document.getElementById('price').addEventListener('input', function() {
box-shadow: 0 4px 6px rgba(0,0,0,0.1); this.value = this.value.replace(/[^0-9]/g, '');
border-radius: 12px; });
}
.card-header { // ===== DRAG & DROP =====
background: linear-gradient(45deg, #f8f9fa, #e9ecef); const uploadArea = document.getElementById('uploadArea');
border-bottom: 1px solid #dee2e6; ['dragover','dragenter'].forEach(evt => {
border-radius: 12px 12px 0 0 !important; uploadArea.addEventListener(evt, function(e) {
} e.preventDefault();
this.style.borderColor = 'var(--green-main)';
this.style.background = '#f0fdf4';
});
});
['dragleave','drop'].forEach(evt => {
uploadArea.addEventListener(evt, function(e) {
e.preventDefault();
this.style.borderColor = '#d1d5db';
this.style.background = '#fafafa';
});
});
.form-label { // Auto-dismiss alerts
font-weight: 600; document.addEventListener('DOMContentLoaded', function() {
color: #495057; document.querySelectorAll('.alert').forEach(function(alert) {
} setTimeout(function() {
const a = bootstrap.Alert.getOrCreateInstance(alert);
.text-danger { if (a) a.close();
color: #dc3545 !important; }, 5000);
} });
document.getElementById('name').focus();
.fade-in { });
animation: fadeIn 0.3s ease-in-out; </script>
} </body>
</html>
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
@endsection

File diff suppressed because it is too large Load Diff

View File

@ -9,137 +9,442 @@
<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">
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { 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; }
:root {
--sidebar-width: 260px;
--green-dark: #15803d;
--green-main: #16a34a;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f1f5f2;
overflow-x: hidden;
}
/* ===== SIDEBAR ===== */
.sidebar { .sidebar {
width: 280px; background: linear-gradient(180deg, #28a745, #20c997); width: var(--sidebar-width);
color: white; min-height: 100vh; position: fixed; top: 0; left: 0; z-index: 1000; background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
color: white;
min-height: 100vh;
position: fixed;
top: 0; left: 0;
z-index: 1050;
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
} }
.sidebar-header { .sidebar-header {
padding: 25px 20px; text-align: center; padding: 24px 20px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1); text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
} }
.sidebar-header h3 { font-size: 1.8rem; font-weight: bold; margin-bottom: 5px; }
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; } .sidebar-header .brand-icon { font-size: 2rem; margin-bottom: 6px; display: block; }
.sidebar-menu { padding: 20px 0; } .sidebar-header h3 { font-size: 1.35rem; font-weight: 700; margin-bottom: 2px; }
.sidebar-header .subtitle { opacity: 0.65; font-size: 0.78rem; margin-bottom: 6px; }
.sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.sidebar-menu { padding: 12px 0; flex: 1; }
.menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item { .menu-item {
display: block; padding: 15px 25px; color: white; display: flex;
text-decoration: none; transition: all 0.3s; border-left: 4px solid transparent; align-items: center;
gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none;
transition: all 0.2s;
border-left: 3px solid transparent;
font-size: 0.9rem;
} }
.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; }
.main-content { margin-left: 280px; flex: 1; min-height: 100vh; } .menu-item i { width: 18px; font-size: 0.95rem; flex-shrink: 0; text-align: center; }
.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; }
.breadcrumb { background: transparent; padding: 0; margin-bottom: 10px; }
.breadcrumb-item a { color: #28a745; text-decoration: none; }
.back-button { .menu-item:hover {
position: fixed; top: 20px; right: 20px; z-index: 1100; background: rgba(255,255,255,0.1);
background: linear-gradient(45deg, #dc3545, #c82333); color: white;
color: white; border: none; padding: 12px 20px; text-decoration: none;
border-radius: 25px; font-weight: 600; text-decoration: none; transition: all 0.3s; border-left-color: rgba(255,255,255,0.5);
} }
.back-button:hover { color: white; transform: translateY(-2px); }
.fade-in { animation: fadeIn 0.3s ease-in-out; } .menu-item.active {
background: rgba(255,255,255,0.15);
color: white;
border-left-color: #fff;
font-weight: 600;
}
.sidebar-footer {
padding: 16px 20px;
border-top: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
.btn-logout {
width: 100%;
background: rgba(255,255,255,0.12);
color: white;
border: 1.5px solid rgba(255,255,255,0.3);
padding: 9px 16px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.5);
color: white;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content {
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== TOPBAR ===== */
.topbar {
background: white;
height: 64px;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 0 #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.topbar-left { display: flex; align-items: center; gap: 14px; }
.hamburger-btn {
display: none;
background: none;
border: none;
color: #374151;
font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
.hamburger-btn:hover { background: #f3f4f6; }
.topbar-breadcrumb { font-size: 0.8rem; color: #6b7280; margin-bottom: 2px; }
.topbar-breadcrumb a { color: var(--green-main); text-decoration: none; }
.topbar-breadcrumb a:hover { text-decoration: underline; }
.topbar-title h2 { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.topbar-title p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.topbar-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.btn-topbar {
border: none;
padding: 8px 14px;
border-radius: 8px;
font-weight: 600;
font-size: 0.8rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
white-space: nowrap;
cursor: pointer;
}
.btn-topbar:hover { transform: translateY(-1px); text-decoration: none; }
.btn-topbar-back {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
}
.btn-topbar-back:hover { box-shadow: 0 4px 12px rgba(220,38,38,0.35); color: white; }
.btn-topbar-save {
background: linear-gradient(135deg, var(--green-main), var(--green-dark));
color: white;
}
.btn-topbar-save:hover { box-shadow: 0 4px 12px rgba(22,163,74,0.35); color: white; }
/* ===== CONTENT AREA ===== */
.content-area { padding: 20px 24px 32px; flex: 1; }
/* ===== INFO CARD ===== */
.info-card {
background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
overflow: hidden;
margin-bottom: 16px;
}
.info-card-header {
padding: 16px 20px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
gap: 10px;
}
.info-card-icon {
width: 34px; height: 34px;
border-radius: 9px;
display: flex; align-items: center; justify-content: center;
font-size: 0.9rem;
flex-shrink: 0;
}
.icon-blue { background: #eff6ff; color: #2563eb; }
.icon-green { background: #f0fdf4; color: var(--green-main); }
.icon-orange { background: #fff7ed; color: #ea580c; }
.icon-purple { background: #faf5ff; color: #7c3aed; }
.icon-gray { background: #f3f4f6; color: #6b7280; }
.info-card-header h3 { font-size: 0.9rem; font-weight: 700; color: #111827; margin: 0; }
.info-card-header p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.info-card-body { padding: 20px; }
/* ===== SECTION DIVIDER ===== */
.section-divider {
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #9ca3af;
padding-bottom: 10px;
border-bottom: 1px solid #f3f4f6;
margin-bottom: 16px;
}
/* ===== FORM STYLES ===== */
.form-label-custom {
font-size: 0.78rem;
font-weight: 600;
color: #374151;
margin-bottom: 5px;
display: block;
}
.form-control-custom,
.form-select-custom {
font-size: 0.84rem;
padding: 8px 12px;
border: 1.5px solid #e5e7eb;
border-radius: 8px;
width: 100%;
color: #111827;
background: white;
transition: border-color 0.2s, box-shadow 0.2s;
outline: none;
}
.form-control-custom:focus,
.form-select-custom:focus {
border-color: var(--green-main);
box-shadow: 0 0 0 3px rgba(22,163,74,0.12);
}
.form-control-custom.readonly,
.form-control-custom[readonly] {
background: #f9fafb;
color: #6b7280;
cursor: not-allowed;
}
.form-control-custom.is-invalid,
.form-select-custom.is-invalid {
border-color: #dc2626;
}
.invalid-feedback-custom {
font-size: 0.75rem;
color: #dc2626;
margin-top: 4px;
}
textarea.form-control-custom { resize: vertical; min-height: 80px; }
.lock-badge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.7rem;
color: #9ca3af;
background: #f3f4f6;
padding: 2px 8px;
border-radius: 10px;
margin-left: 8px;
font-weight: 500;
}
/* ===== ORDER SUMMARY TABLE ===== */
.summary-table { width: 100%; border-collapse: collapse; font-size: 0.83rem; }
.summary-table td { padding: 7px 0; color: #374151; border-bottom: 1px solid #f9fafb; }
.summary-table td:last-child { text-align: right; font-weight: 600; }
.summary-table tfoot td {
border-top: 2px solid #f3f4f6;
border-bottom: none;
font-weight: 700;
color: var(--green-main);
font-size: 0.9rem;
padding-top: 10px;
}
/* ===== FADE IN ===== */
.fade-in { animation: fadeIn 0.3s ease; }
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); } from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
@media (max-width: 768px) { /* ===== RESPONSIVE ===== */
.sidebar { width: 100%; position: relative; } @media (max-width: 900px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.2); }
.main-content { margin-left: 0; } .main-content { margin-left: 0; }
.hamburger-btn { display: flex; align-items: center; justify-content: center; }
}
@media (max-width: 767px) {
.content-area { padding: 14px 14px 24px; }
.topbar { padding: 0 14px; height: auto; min-height: 58px; flex-wrap: wrap; gap: 8px; padding-top: 10px; padding-bottom: 10px; }
.topbar-actions .btn-topbar span { display: none; }
.topbar-actions .btn-topbar { padding: 8px 10px; }
} }
</style> </style>
</head> </head>
<body> <body>
<a href="{{ route('home') }}" class="back-button"> <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
</a>
<div class="admin-container"> <!-- ===== SIDEBAR ===== -->
<nav class="sidebar" id="sidebar">
{{-- Sidebar --}}
<nav class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<h3>🌱 Bibit Cabai</h3> <span class="brand-icon">🌱</span>
<p>Admin Dashboard</p> <h3>Bibit Cabai</h3>
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small> <p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div> </div>
<div class="sidebar-menu"> <div class="sidebar-menu">
<div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.dashboard') }}" class="menu-item"> <a href="{{ route('admin.dashboard') }}" class="menu-item">
<i class="fas fa-tachometer-alt"></i>Dashboard <i class="fas fa-tachometer-alt"></i><span>Dashboard</span>
</a> </a>
<a href="{{ route('admin.products.index') }}" class="menu-item"> <a href="{{ route('admin.products.index') }}" class="menu-item">
<i class="fas fa-seedling"></i>Kelola Produk <i class="fas fa-seedling"></i><span>Kelola Produk</span>
</a> </a>
<a href="{{ route('admin.orders') }}" class="menu-item active"> <a href="{{ route('admin.orders') }}" class="menu-item active">
<i class="fas fa-shopping-cart"></i>Pesanan <i class="fas fa-shopping-cart"></i><span>Pesanan</span>
</a> </a>
<a href="{{ route('admin.cancellations') }}" class="menu-item"> <a href="{{ route('admin.cancellations') }}" class="menu-item">
<i class="fas fa-times-circle"></i>Pengajuan Batal <i class="fas fa-times-circle"></i><span>Pengajuan Batal</span>
</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><span>Pengguna</span>
</a> </a>
<a href="{{ route('admin.laporan') }}" 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><span>Laporan</span>
</a> </a>
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"> </div>
<i class="fas fa-cog"></i>Pengaturan --> <div class="sidebar-footer">
</a>
<div class="mt-4 px-3">
<form method="POST" action="{{ route('admin.logout') }}"> <form method="POST" action="{{ route('admin.logout') }}">
@csrf @csrf
<button type="submit" class="btn btn-outline-light btn-sm w-100"> <button type="submit" class="btn-logout">
<i class="fas fa-sign-out-alt me-2"></i>Logout <i class="fas fa-sign-out-alt"></i><span>Logout</span>
</button> </button>
</form> </form>
</div> </div>
</div>
</nav> </nav>
{{-- Main Content --}} <!-- ===== MAIN ===== -->
<div class="main-content"> <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.orders') }}">Pesanan</a></li>
<li class="breadcrumb-item active">Edit {{ $transaksi->invoice_number }}</li>
</ol>
</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>
<!-- Topbar -->
<header class="topbar">
<div class="topbar-left">
<button class="hamburger-btn" onclick="toggleSidebar()">
<i class="fas fa-bars"></i>
</button>
<div class="topbar-title">
<div class="topbar-breadcrumb">
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
&nbsp;/&nbsp;<a href="{{ route('admin.orders') }}">Pesanan</a>
&nbsp;/&nbsp;<a href="{{ route('admin.transaksis.show', $transaksi->id) }}">{{ $transaksi->invoice_number }}</a>
&nbsp;/&nbsp;Edit
</div>
<h2>Edit Order</h2>
<p>{{ $transaksi->invoice_number }}</p>
</div>
</div>
<div class="topbar-actions">
<a href="{{ route('admin.transaksis.show', $transaksi->id) }}" class="btn-topbar btn-topbar-back">
<i class="fas fa-arrow-left"></i>
<span>Kembali</span>
</a>
<button type="submit" form="editOrderForm" class="btn-topbar btn-topbar-save">
<i class="fas fa-save"></i>
<span>Simpan</span>
</button>
</div>
</header>
<!-- Content -->
<div class="content-area fade-in"> <div class="content-area fade-in">
@if ($errors->any()) @if ($errors->any())
<div class="alert alert-danger alert-dismissible fade show"> <div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
<strong>Error!</strong> <i class="fas fa-exclamation-circle me-2"></i><strong>Error!</strong>
<ul class="mb-0 mt-2"> <ul class="mb-0 mt-1">
@foreach ($errors->all() as $error) @foreach ($errors->all() as $error)
<li>{{ $error }}</li> <li style="font-size:0.85rem;">{{ $error }}</li>
@endforeach @endforeach
</ul> </ul>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
@ -147,216 +452,235 @@
@endif @endif
@if(session('success')) @if(session('success'))
<div class="alert alert-success alert-dismissible fade show"> <div class="alert alert-success alert-dismissible fade show mb-4" role="alert">
<i class="fas fa-check-circle me-2"></i>{{ session('success') }} <i class="fas fa-check-circle me-2"></i>{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div> </div>
@endif @endif
<form action="{{ route('admin.transaksis.update', $transaksi->id) }}" method="POST"> <form id="editOrderForm" action="{{ route('admin.transaksis.update', $transaksi->id) }}" method="POST">
@csrf @csrf
@method('PUT') @method('PUT')
<div class="row"> <div class="row g-3">
{{-- Kolom Kiri --}} <!-- ===== KOLOM KIRI (8/12) ===== -->
<div class="col-lg-8"> <div class="col-lg-8">
{{-- Order Details --}} <!-- Order Details (editable) -->
<div class="card shadow mb-4" style="border-radius:12px;"> <div class="info-card">
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;"> <div class="info-card-header">
<h6 class="m-0 fw-bold text-primary">Order Details</h6> <div class="info-card-icon icon-blue">
<i class="fas fa-file-alt"></i>
</div> </div>
<div class="card-body"> <div>
<h3>Order Details</h3>
<p>Status & informasi pesanan yang dapat diubah</p>
</div>
</div>
<div class="info-card-body">
<div class="row g-4">
<div class="col-md-6">
<div class="section-divider">Invoice</div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-semibold">Invoice Number</label> <label class="form-label-custom">Invoice Number</label>
<input type="text" class="form-control bg-light" <input type="text" class="form-control-custom readonly"
value="{{ $transaksi->invoice_number }}" readonly> value="{{ $transaksi->invoice_number }}" readonly>
</div> </div>
<div class="row">
<div class="col-md-6">
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-semibold">Order Status <span class="text-danger">*</span></label> <label class="form-label-custom">Tanggal Order</label>
<select class="form-select @error('order_status') is-invalid @enderror" <input type="text" class="form-control-custom readonly"
name="order_status" required> value="{{ $transaksi->created_at->format('d M Y, H:i') }}" readonly>
</div>
</div>
<div class="col-md-6">
<div class="section-divider">Update Status</div>
<div class="mb-3">
<label class="form-label-custom">Order Status <span style="color:#dc2626;">*</span></label>
<select name="order_status" class="form-select-custom @error('order_status') is-invalid @enderror" required>
@foreach(['pending','processing','shipped','delivered','cancelled'] as $s) @foreach(['pending','processing','shipped','delivered','cancelled'] as $s)
<option value="{{ $s }}" <option value="{{ $s }}" {{ old('order_status', $transaksi->order_status) == $s ? 'selected' : '' }}>
{{ old('order_status', $transaksi->order_status) == $s ? 'selected' : '' }}>
{{ ucfirst($s) }} {{ ucfirst($s) }}
</option> </option>
@endforeach @endforeach
</select> </select>
@error('order_status') @error('order_status')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback-custom">{{ $message }}</div>
@enderror @enderror
</div> </div>
</div>
<div class="col-md-6">
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-semibold">Payment Status <span class="text-danger">*</span></label> <label class="form-label-custom">Payment Status <span style="color:#dc2626;">*</span></label>
<select class="form-select @error('payment_status') is-invalid @enderror" <select name="payment_status" class="form-select-custom @error('payment_status') is-invalid @enderror" required>
name="payment_status" required>
@foreach(['pending','paid','failed'] as $p) @foreach(['pending','paid','failed'] as $p)
<option value="{{ $p }}" <option value="{{ $p }}" {{ old('payment_status', $transaksi->payment_status) == $p ? 'selected' : '' }}>
{{ old('payment_status', $transaksi->payment_status) == $p ? 'selected' : '' }}>
{{ ucfirst($p) }} {{ ucfirst($p) }}
</option> </option>
@endforeach @endforeach
</select> </select>
@error('payment_status') @error('payment_status')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback-custom">{{ $message }}</div>
@enderror @enderror
</div> </div>
</div> </div>
</div> </div>
<div class="mb-3">
<label class="form-label fw-semibold">Tracking Number</label> <div class="row g-3 mt-1">
<input type="text" class="form-control @error('tracking_number') is-invalid @enderror" <div class="col-md-6">
name="tracking_number" <label class="form-label-custom">Tracking Number</label>
<input type="text" name="tracking_number"
class="form-control-custom @error('tracking_number') is-invalid @enderror"
value="{{ old('tracking_number', $transaksi->tracking_number) }}" value="{{ old('tracking_number', $transaksi->tracking_number) }}"
placeholder="Enter tracking number"> placeholder="Masukkan nomor resi...">
@error('tracking_number') @error('tracking_number')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback-custom">{{ $message }}</div>
@enderror @enderror
</div> </div>
<div class="mb-3"> <div class="col-md-6">
<label class="form-label fw-semibold">Notes</label> <label class="form-label-custom">Catatan / Notes</label>
<textarea class="form-control @error('notes') is-invalid @enderror" <textarea name="notes"
name="notes" rows="3" class="form-control-custom @error('notes') is-invalid @enderror"
placeholder="Enter any notes...">{{ old('notes', $transaksi->notes) }}</textarea> placeholder="Catatan tambahan...">{{ old('notes', $transaksi->notes) }}</textarea>
@error('notes') @error('notes')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback-custom">{{ $message }}</div>
@enderror @enderror
</div> </div>
</div> </div>
</div> </div>
</div>
{{-- Customer Information (readonly) --}} <!-- Customer Information (readonly) -->
<div class="card shadow mb-4" style="border-radius:12px;"> <div class="info-card">
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;"> <div class="info-card-header">
<h6 class="m-0 fw-bold text-primary"> <div class="info-card-icon icon-purple">
Customer Information <i class="fas fa-user"></i>
<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>
<div class="card-body"> <div>
<div class="mb-3"> <h3>Customer Information</h3>
<label class="form-label fw-semibold">Customer Name</label> <p>Informasi pembeli <span class="lock-badge"><i class="fas fa-lock"></i> Tidak dapat diubah</span></p>
<input type="text" class="form-control bg-light"
value="{{ $transaksi->customer_name }}" readonly>
</div> </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="info-card-body">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label-custom">Nama Customer</label>
<input type="text" class="form-control-custom" value="{{ $transaksi->customer_name }}" readonly>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="mb-3"> <label class="form-label-custom">Telepon</label>
<label class="form-label fw-semibold">Email</label> <input type="text" class="form-control-custom" value="{{ $transaksi->customer_phone }}" readonly>
<input type="text" class="form-control bg-light"
value="{{ $transaksi->customer_email ?? '-' }}" readonly>
</div> </div>
<div class="col-md-6">
<label class="form-label-custom">Email</label>
<input type="text" class="form-control-custom" value="{{ $transaksi->customer_email ?? '-' }}" readonly>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{{-- Shipping Information (readonly) --}} <!-- Shipping Information (readonly) -->
<div class="card shadow mb-4" style="border-radius:12px;"> <div class="info-card">
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;"> <div class="info-card-header">
<h6 class="m-0 fw-bold text-primary"> <div class="info-card-icon icon-orange">
Shipping Information <i class="fas fa-truck"></i>
<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>
<div class="card-body"> <div>
<div class="mb-3"> <h3>Pengiriman</h3>
<label class="form-label fw-semibold">Shipping Address</label> <p>Alamat & info pengiriman <span class="lock-badge"><i class="fas fa-lock"></i> Tidak dapat diubah</span></p>
<textarea class="form-control bg-light" rows="3"
readonly>{{ $transaksi->shipping_address }}</textarea>
</div> </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="info-card-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label-custom">Alamat Pengiriman</label>
<textarea class="form-control-custom" rows="3" readonly>{{ $transaksi->shipping_address }}</textarea>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="mb-3"> <label class="form-label-custom">Kota</label>
<label class="form-label fw-semibold">Province</label> <input type="text" class="form-control-custom" value="{{ $transaksi->city }}" readonly>
<input type="text" class="form-control bg-light"
value="{{ $transaksi->province }}" readonly>
</div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="mb-3"> <label class="form-label-custom">Provinsi</label>
<label class="form-label fw-semibold">Postal Code</label> <input type="text" class="form-control-custom" value="{{ $transaksi->province }}" readonly>
<input type="text" class="form-control bg-light"
value="{{ $transaksi->postal_code }}" readonly>
</div> </div>
<div class="col-md-4">
<label class="form-label-custom">Kode Pos</label>
<input type="text" class="form-control-custom" value="{{ $transaksi->postal_code }}" readonly>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{{-- Akhir col-lg-8 --}} <!-- Akhir col-lg-8 -->
{{-- Kolom Kanan --}} <!-- ===== KOLOM KANAN (4/12) ===== -->
<div class="col-lg-4"> <div class="col-lg-4">
{{-- Order Summary --}} <!-- Order Summary -->
<div class="card shadow mb-4" style="border-radius:12px;"> <div class="info-card">
<div class="card-header py-3" style="background:#f8f9fa; border-radius:12px 12px 0 0;"> <div class="info-card-header">
<h6 class="m-0 fw-bold text-primary">Order Summary</h6> <div class="info-card-icon icon-green">
<i class="fas fa-box"></i>
</div> </div>
<div class="card-body"> <div>
<h5 class="text-primary">{{ $transaksi->invoice_number }}</h5> <h3>Order Summary</h3>
<p class="text-muted mb-3">{{ $transaksi->created_at->format('d M Y, H:i') }}</p> <p>Ringkasan produk pesanan</p>
<table class="table table-sm table-borderless"> </div>
</div>
<div class="info-card-body">
<table class="summary-table">
<tbody> <tbody>
@foreach($transaksi->details as $detail) @foreach($transaksi->details as $detail)
<tr> <tr>
<td>{{ $detail->product->name ?? 'N/A' }}</td> <td>{{ $detail->product->name ?? 'N/A' }}</td>
<td class="text-end">x{{ $detail->quantity }}</td> <td style="text-align:center;color:#9ca3af;">x{{ $detail->quantity }}</td>
<td class="text-end">Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</td> <td>Rp {{ number_format($detail->subtotal, 0, ',', '.') }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
<tfoot class="border-top"> <tfoot>
<tr> <tr>
<th colspan="2">Total:</th> <td colspan="2" style="font-size:0.8rem;color:#6b7280;">Total Amount</td>
<th class="text-end">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</th> <td style="color:var(--green-main);font-size:0.95rem;">Rp {{ number_format($transaksi->total_amount, 0, ',', '.') }}</td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
</div> </div>
</div> </div>
{{-- Action Buttons --}} <!-- Action Buttons -->
<div class="card shadow" style="border-radius:12px;"> <div class="info-card">
<div class="card-body"> <div class="info-card-header">
<button type="submit" class="btn btn-primary w-100 mb-2"> <div class="info-card-icon icon-gray">
<i class="fas fa-save me-1"></i> Update Order <i class="fas fa-cog"></i>
</div>
<div>
<h3>Aksi</h3>
<p>Simpan atau batalkan perubahan</p>
</div>
</div>
<div class="info-card-body" style="display:flex;flex-direction:column;gap:10px;">
<button type="submit" form="editOrderForm"
style="width:100%;padding:10px 16px;border-radius:9px;border:none;
background:linear-gradient(135deg,var(--green-main),var(--green-dark));
color:white;font-weight:700;font-size:0.875rem;cursor:pointer;
display:flex;align-items:center;justify-content:center;gap:8px;
transition:all 0.2s;">
<i class="fas fa-save"></i> Update Order
</button> </button>
<a href="{{ route('admin.transaksis.show', $transaksi->id) }}" <a href="{{ route('admin.transaksis.show', $transaksi->id) }}"
class="btn btn-secondary w-100"> style="width:100%;padding:10px 16px;border-radius:9px;
<i class="fas fa-times me-1"></i> Cancel border:1.5px solid #e5e7eb;background:#f9fafb;
color:#374151;font-weight:600;font-size:0.875rem;cursor:pointer;
display:flex;align-items:center;justify-content:center;gap:8px;
text-decoration:none;transition:all 0.2s;">
<i class="fas fa-times"></i> Batal
</a> </a>
</div> </div>
</div> </div>
</div> </div>
{{-- Akhir col-lg-4 --}} <!-- Akhir col-lg-4 -->
</div> </div>
</form> </form>
@ -364,8 +688,28 @@ class="btn btn-secondary w-100">
</div> </div>
</div> </div>
</div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
document.getElementById('sidebarOverlay').classList.toggle('show');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('sidebarOverlay').classList.remove('show');
}
window.addEventListener('resize', function() {
if (window.innerWidth > 900) closeSidebar();
});
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.alert').forEach(function(alert) {
setTimeout(function() {
const a = bootstrap.Alert.getOrCreateInstance(alert);
if (a) a.close();
}, 5000);
});
});
</script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@ -6,331 +6,530 @@
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<title>Kelola Pengguna - Bibit Cabai 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"> <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">
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--sidebar-width: 260px;
--green-dark: #15803d;
--green-main: #16a34a;
}
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8f9fa; background: #f1f5f2;
overflow-x: hidden;
} }
.admin-container { /* ===== SIDEBAR ===== */
display: flex;
min-height: 100vh;
}
/* Sidebar */
.sidebar { .sidebar {
width: 280px; width: var(--sidebar-width);
background: linear-gradient(180deg, #28a745, #20c997); background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
color: white; color: white;
min-height: 100vh; min-height: 100vh;
position: fixed; position: fixed;
top: 0; top: 0; left: 0;
left: 0; z-index: 1050;
z-index: 1000; transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
} }
.sidebar-header { .sidebar-header {
padding: 25px 20px; padding: 24px 20px 20px;
text-align: center; text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.1); border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
} }
.sidebar-header h3 { .sidebar-header .brand-icon { font-size: 2rem; margin-bottom: 6px; display: block; }
font-size: 1.8rem; .sidebar-header h3 { font-size: 1.35rem; font-weight: 700; margin-bottom: 2px; }
font-weight: bold; .sidebar-header .subtitle { opacity: 0.65; font-size: 0.78rem; margin-bottom: 6px; }
margin-bottom: 5px; .sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
} }
.sidebar-header p { opacity: 0.8; font-size: 0.9rem; } .sidebar-menu { padding: 12px 0; flex: 1; }
.sidebar-menu { padding: 20px 0; } .menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item { .menu-item {
display: block; display: flex;
padding: 15px 25px; align-items: center;
color: white; gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none; text-decoration: none;
transition: all 0.3s; transition: all 0.2s;
border-left: 4px solid transparent; border-left: 3px solid transparent;
font-size: 0.9rem;
} }
.menu-item:hover, .menu-item.active { .menu-item i { width: 18px; font-size: 0.95rem; flex-shrink: 0; text-align: center; }
background: rgba(255,255,255,0.2);
.menu-item:hover {
background: rgba(255,255,255,0.1);
color: white; color: white;
text-decoration: none; text-decoration: none;
border-left-color: rgba(255,255,255,0.5);
}
.menu-item.active {
background: rgba(255,255,255,0.15);
color: white;
border-left-color: #fff; border-left-color: #fff;
transform: translateX(5px); font-weight: 600;
} }
.menu-item i { width: 20px; margin-right: 10px; } .sidebar-footer {
padding: 16px 20px;
border-top: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
/* Main Content */ .btn-logout {
width: 100%;
background: rgba(255,255,255,0.12);
color: white;
border: 1.5px solid rgba(255,255,255,0.3);
padding: 9px 16px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.5);
color: white;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content { .main-content {
margin-left: 280px; margin-left: var(--sidebar-width);
flex: 1;
min-height: 100vh; min-height: 100vh;
display: flex;
flex-direction: column;
} }
.admin-header { /* ===== TOPBAR ===== */
.topbar {
background: white; background: white;
padding: 20px 30px; height: 64px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 0 24px;
border-bottom: 1px solid #dee2e6; display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 0 #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
} }
.content-area { padding: 30px; } .topbar-left { display: flex; align-items: center; gap: 14px; }
.page-header { .hamburger-btn {
background: white; display: none;
padding: 30px; background: none;
border-radius: 12px; border: none;
box-shadow: 0 4px 6px rgba(0,0,0,0.05); color: #374151;
margin-bottom: 30px; font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
} }
.breadcrumb { background: transparent; padding: 0; margin-bottom: 10px; } .hamburger-btn:hover { background: #f3f4f6; }
.breadcrumb-item a { color: #28a745; text-decoration: none; }
/* Stats Cards */ .topbar-breadcrumb { font-size: 0.8rem; color: #6b7280; margin-bottom: 2px; }
.topbar-breadcrumb a { color: var(--green-main); text-decoration: none; }
.topbar-title h2 { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.topbar-title p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.btn-back-website {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
border: none;
padding: 9px 18px;
border-radius: 8px;
font-weight: 600;
font-size: 0.825rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.2s;
white-space: nowrap;
}
.btn-back-website:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220,38,38,0.35);
color: white;
text-decoration: none;
}
/* ===== CONTENT AREA ===== */
.content-area { padding: 20px 24px 24px; flex: 1; }
/* ===== STATS GRID ===== */
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 20px; gap: 16px;
margin-bottom: 30px; margin-bottom: 20px;
} }
.stat-card { .stat-card {
background: white; background: white;
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 18px 16px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05); box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-left: 4px solid; border-left: 4px solid;
} }
.stat-card.primary { border-left-color: #007bff; } .stat-card.primary { border-left-color: #2563eb; }
.stat-card.warning { border-left-color: #ffc107; } .stat-card.warning { border-left-color: #d97706; }
.stat-card.info { border-left-color: #17a2b8; } .stat-card.success { border-left-color: var(--green-main); }
.stat-card.success { border-left-color: #28a745; } .stat-card.info { border-left-color: #0891b2; }
.stat-label { .stat-label {
font-size: 0.75rem; font-size: 0.72rem;
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
margin-bottom: 5px; letter-spacing: 0.04em;
margin-bottom: 4px;
} }
.stat-card.primary .stat-label { color: #007bff; } .stat-card.primary .stat-label { color: #2563eb; }
.stat-card.warning .stat-label { color: #ffc107; } .stat-card.warning .stat-label { color: #d97706; }
.stat-card.info .stat-label { color: #17a2b8; } .stat-card.success .stat-label { color: var(--green-main); }
.stat-card.success .stat-label { color: #28a745; } .stat-card.info .stat-label { color: #0891b2; }
.stat-value { font-size: 1.5rem; font-weight: 700; color: #333; } .stat-value { font-size: 1.5rem; font-weight: 800; color: #111827; }
.stat-icon { font-size: 2rem; color: #dee2e6; } .stat-icon { font-size: 1.75rem; color: #e5e7eb; }
/* Section Header */ /* ===== TABLE CARD ===== */
.section-header { .table-card {
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; background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
overflow: hidden;
} }
.table-card-header {
padding: 18px 20px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.section-title {
font-size: 1rem;
font-weight: 700;
color: #111827;
display: flex;
align-items: center;
gap: 8px;
}
/* Search */
.search-bar {
padding: 14px 20px;
border-bottom: 1px solid #f3f4f6;
}
/* ===== TABLE ===== */
.data-table { width: 100%; border-collapse: collapse; }
.data-table th, .data-table td { .data-table th, .data-table td {
padding: 12px 15px; padding: 11px 14px;
text-align: left; text-align: left;
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #f3f4f6;
font-size: 0.875rem;
vertical-align: middle;
} }
.data-table th { .data-table th {
background: #f8f9fa; background: #f9fafb;
font-weight: 600; font-weight: 600;
color: #495057; color: #6b7280;
font-size: 0.85rem; font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
} }
.data-table tbody tr:hover { background-color: #f8f9fc; } .data-table tbody tr:hover { background: #f9fafb; }
/* Badges */ /* ===== BADGES ===== */
.badge-role { .badge-role {
padding: 4px 10px; padding: 4px 10px;
border-radius: 12px; border-radius: 12px;
font-size: 0.75rem; font-size: 0.72rem;
font-weight: 600; font-weight: 600;
display: inline-block;
} }
.badge-admin { background: #cce5ff; color: #004085; } .badge-admin { background: #dbeafe; color: #1e40af; }
.badge-user { background: #d4edda; color: #155724; } .badge-user { background: #dcfce7; color: #166534; }
.badge-verified { background: #d4edda; color: #155724; } .badge-verified { background: #dcfce7; color: #166534; }
.badge-unverified { background: #e2e3e5; color: #383d41; } .badge-unverified { background: #f3f4f6; color: #6b7280; }
/* Action Buttons */ /* ===== ACTION BUTTONS ===== */
.btn-action { .btn-action {
padding: 6px 10px; padding: 6px 9px;
border: none; border: none;
border-radius: 6px; border-radius: 6px;
font-size: 0.8rem; font-size: 0.8rem;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.2s;
text-decoration: none; text-decoration: none;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center;
} }
.btn-action:hover { .btn-action:hover {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.2); box-shadow: 0 2px 6px rgba(0,0,0,0.15);
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
} }
.btn-view { background: #17a2b8; color: white; } .btn-view { background: #0891b2; color: white; }
.btn-edit { background: #ffc107; color: #212529; } .btn-edit { background: #d97706; color: white; }
.btn-delete { background: #dc3545; color: white; } .btn-delete { background: #dc2626; color: white; }
/* Back Button */ /* ===== TABLE FOOTER ===== */
.back-button { .table-footer {
position: fixed; padding: 14px 20px;
top: 20px; border-top: 1px solid #f3f4f6;
right: 20px; display: flex;
z-index: 1100; justify-content: space-between;
background: linear-gradient(45deg, #dc3545, #c82333); align-items: center;
gap: 12px;
flex-wrap: wrap;
}
/* ===== MOBILE CARDS ===== */
.mobile-card-list { display: none; padding: 14px; }
.user-card {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
}
.user-card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
gap: 10px;
}
.user-card-avatar {
width: 42px; height: 42px;
background: linear-gradient(135deg, var(--green-dark), var(--green-main));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white; color: white;
border: none; font-weight: 700;
padding: 12px 20px; font-size: 1rem;
border-radius: 25px; flex-shrink: 0;
font-weight: 600;
transition: all 0.3s;
text-decoration: none;
} }
.back-button:hover { .user-card-name { font-weight: 700; font-size: 0.95rem; color: #111827; }
background: linear-gradient(45deg, #c82333, #a71e2a); .user-card-email { font-size: 0.78rem; color: #9ca3af; margin-top: 2px; }
transform: translateY(-2px);
color: white; .user-card-badges { display: flex; gap: 6px; flex-wrap: wrap; }
text-decoration: none;
.user-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 12px;
font-size: 0.8rem;
} }
.fade-in { .user-card-meta-item .label { color: #9ca3af; font-size: 0.7rem; text-transform: uppercase; font-weight: 600; margin-bottom: 2px; }
animation: fadeIn 0.3s ease-in-out; .user-card-meta-item .value { color: #374151; }
}
.user-card-actions { display: flex; gap: 8px; }
.user-card-actions .btn-action { flex: 1; justify-content: center; padding: 8px; }
/* ===== FADE IN ===== */
.fade-in { animation: fadeIn 0.3s ease; }
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); } from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
@media (max-width: 768px) { /* ===== RESPONSIVE ===== */
.sidebar { width: 100%; position: relative; } @media (max-width: 900px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.2); }
.main-content { margin-left: 0; } .main-content { margin-left: 0; }
.hamburger-btn { display: flex; align-items: center; justify-content: center; }
}
@media (max-width: 1100px) {
.stats-grid { grid-template-columns: repeat(2, 1fr); } .stats-grid { grid-template-columns: repeat(2, 1fr); }
} }
@media (max-width: 767px) {
.desktop-table-wrap { display: none; }
.mobile-card-list { display: block; }
.stats-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
.content-area { padding: 14px 14px 20px; }
.topbar { padding: 0 14px; }
.btn-back-website span { display: none; }
.btn-back-website { padding: 9px 12px; }
.table-card-header { flex-direction: column; align-items: stretch; }
.table-card-header .btn { width: 100%; }
.table-footer { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 400px) {
.stats-grid { grid-template-columns: 1fr; }
}
</style> </style>
</head> </head>
<body> <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"> <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<!-- Sidebar --> <!-- Sidebar -->
<nav class="sidebar"> <nav class="sidebar" id="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<h3>🌱 Bibit Cabai</h3> <span class="brand-icon">🌱</span>
<p>Admin Dashboard</p> <h3>Bibit Cabai</h3>
<small class="text-light">{{ Auth::user()->name ?? 'Admin User' }}</small> <p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div> </div>
<div class="sidebar-menu"> <div class="sidebar-menu">
<div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.dashboard') }}" class="menu-item"> <a href="{{ route('admin.dashboard') }}" class="menu-item">
<i class="fas fa-tachometer-alt"></i>Dashboard <i class="fas fa-tachometer-alt"></i><span>Dashboard</span>
</a> </a>
<a href="{{ route('admin.products.index') }}" class="menu-item"> <a href="{{ route('admin.products.index') }}" class="menu-item">
<i class="fas fa-seedling"></i>Kelola Produk <i class="fas fa-seedling"></i><span>Kelola Produk</span>
</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><span>Pesanan</span>
</a> </a>
{{-- TAMBAHKAN DI SINI --}}
<a href="{{ route('admin.cancellations') }}" class="menu-item"> <a href="{{ route('admin.cancellations') }}" class="menu-item">
<i class="fas fa-times-circle"></i>Pengajuan Batal <i class="fas fa-times-circle"></i><span>Pengajuan Batal</span>
</a> </a>
<a href="{{ route('admin.users') }}" class="menu-item active"> <a href="{{ route('admin.users') }}" class="menu-item active">
<i class="fas fa-users"></i>Pengguna <i class="fas fa-users"></i><span>Pengguna</span>
</a> </a>
<a href="{{ route('admin.laporan') }}" 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><span>Laporan</span>
</a> </a>
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"> </div>
<i class="fas fa-cog"></i>Pengaturan
</a> --> <div class="sidebar-footer">
<div class="mt-4 px-3">
<form method="POST" action="{{ route('admin.logout') }}"> <form method="POST" action="{{ route('admin.logout') }}">
@csrf @csrf
<button type="submit" class="btn btn-outline-light btn-sm w-100"> <button type="submit" class="btn-logout">
<i class="fas fa-sign-out-alt me-2"></i>Logout <i class="fas fa-sign-out-alt"></i><span>Logout</span>
</button> </button>
</form> </form>
</div> </div>
</div>
</nav> </nav>
<!-- Main Content --> <!-- Main Content -->
<div class="main-content"> <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 active" aria-current="page">Pengguna</li>
</ol>
</nav>
<h2>Kelola Pengguna</h2>
<p class="mb-0">Kelola semua pengguna terdaftar di sini!</p>
</div>
<!-- Topbar -->
<header class="topbar">
<div class="topbar-left">
<button class="hamburger-btn" onclick="toggleSidebar()" aria-label="Toggle menu">
<i class="fas fa-bars"></i>
</button>
<div class="topbar-title">
<div class="topbar-breadcrumb">
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
&nbsp;/&nbsp;Pengguna
</div>
<h2>Kelola Pengguna</h2>
<p>Kelola semua pengguna terdaftar di sini!</p>
</div>
</div>
<a href="{{ route('home') }}" class="btn-back-website">
<i class="fas fa-arrow-left"></i>
<span>Kembali ke Website</span>
</a>
</header>
<!-- Content -->
<div class="content-area fade-in"> <div class="content-area fade-in">
@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 mb-4" role="alert">
<i class="fas fa-check-circle me-2"></i>{{ session('success') }} <i class="fas fa-check-circle me-2"></i>{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div> </div>
@endif @endif
@if(session('error')) @if(session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert"> <div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }} <i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div> </div>
@endif @endif
<!-- Stats Cards --> <!-- Stats -->
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card primary"> <div class="stat-card primary">
<div> <div>
@ -362,21 +561,21 @@
</div> </div>
</div> </div>
<!-- Users Table --> <!-- Table Card -->
<div class="page-header"> <div class="table-card">
<div class="section-header"> <div class="table-card-header">
<h3 class="section-title"> <div class="section-title">
<i class="fas fa-users text-success me-2"></i> <i class="fas fa-users text-success"></i>
Daftar Pengguna ({{ $totalUsers ?? 0 }}) Daftar Pengguna ({{ $totalUsers ?? 0 }})
</h3> </div>
<a href="{{ route('admin.users.create') }}" class="btn btn-primary btn-sm"> <a href="{{ route('admin.users.create') }}" class="btn btn-primary btn-sm">
<i class="fas fa-plus me-1"></i>Tambah Pengguna <i class="fas fa-plus me-1"></i>Tambah Pengguna
</a> </a>
</div> </div>
<!-- Search --> <!-- Search -->
<div class="mb-3"> <div class="search-bar">
<div class="input-group" style="max-width: 400px;"> <div class="input-group" style="max-width:400px;">
<span class="input-group-text"><i class="fas fa-search"></i></span> <span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" id="searchInput" <input type="text" class="form-control" id="searchInput"
placeholder="Cari nama, email, atau telepon..."> placeholder="Cari nama, email, atau telepon...">
@ -384,11 +583,13 @@
</div> </div>
@if(isset($users) && $users->count() > 0) @if(isset($users) && $users->count() > 0)
<div class="table-responsive">
<!-- DESKTOP TABLE -->
<div class="desktop-table-wrap table-responsive">
<table class="data-table" id="usersTable"> <table class="data-table" id="usersTable">
<thead> <thead>
<tr> <tr>
<th>ID</th> <th style="padding-left:20px;">ID</th>
<th>Nama</th> <th>Nama</th>
<th>Email</th> <th>Email</th>
<th>Telepon</th> <th>Telepon</th>
@ -401,11 +602,13 @@
<tbody> <tbody>
@foreach($users as $user) @foreach($users as $user)
<tr> <tr>
<td>{{ ($users->currentPage() - 1) * $users->perPage() + $loop->iteration }}</td> <td style="padding-left:20px; color:#6b7280;">
<td><strong>{{ $user->name }}</strong></td> {{ ($users->currentPage() - 1) * $users->perPage() + $loop->iteration }}
<td>{{ $user->email }}</td> </td>
<td>{{ $user->phone ?? '-' }}</td> <td><strong style="color:#111827;">{{ $user->name }}</strong></td>
<td><small>{{ \Illuminate\Support\Str::limit($user->address ?? '-', 30) }}</small></td> <td style="color:#374151;">{{ $user->email }}</td>
<td style="color:#374151;">{{ $user->phone ?? '-' }}</td>
<td><small style="color:#6b7280;">{{ \Illuminate\Support\Str::limit($user->address ?? '-', 30) }}</small></td>
<td> <td>
@if($user->is_admin == 1) @if($user->is_admin == 1)
<span class="badge-role badge-admin">Admin</span> <span class="badge-role badge-admin">Admin</span>
@ -433,7 +636,7 @@ class="btn-action btn-edit" title="Edit">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</a> </a>
<form method="POST" action="{{ route('admin.users.destroy', $user) }}" <form method="POST" action="{{ route('admin.users.destroy', $user) }}"
style="display: inline;" style="display:inline;"
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')"> onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
@csrf @csrf
@method('DELETE') @method('DELETE')
@ -449,38 +652,134 @@ class="btn-action btn-edit" title="Edit">
</table> </table>
</div> </div>
<!-- MOBILE CARD LIST -->
<div class="mobile-card-list" id="mobileUserList">
@foreach($users as $user)
<div class="user-card" data-search="{{ strtolower($user->name . ' ' . $user->email . ' ' . ($user->phone ?? '')) }}">
<div class="user-card-top">
<div class="d-flex align-items-center gap-3">
<div class="user-card-avatar">
{{ strtoupper(substr($user->name, 0, 1)) }}
</div>
<div>
<div class="user-card-name">{{ $user->name }}</div>
<div class="user-card-email">{{ $user->email }}</div>
</div>
</div>
<div class="user-card-badges">
@if($user->is_admin == 1)
<span class="badge-role badge-admin">Admin</span>
@else
<span class="badge-role badge-user">User</span>
@endif
</div>
</div>
<div class="user-card-meta">
<div class="user-card-meta-item">
<div class="label">Telepon</div>
<div class="value">{{ $user->phone ?? '-' }}</div>
</div>
<div class="user-card-meta-item">
<div class="label">Status Email</div>
<div class="value">
@if($user->email_verified_at)
<span class="badge-role badge-verified" style="font-size:0.68rem;">
<i class="fas fa-check me-1"></i>Terverifikasi
</span>
@else
<span class="badge-role badge-unverified" style="font-size:0.68rem;">Belum Verifikasi</span>
@endif
</div>
</div>
@if($user->address)
<div class="user-card-meta-item" style="grid-column:span 2;">
<div class="label">Alamat</div>
<div class="value">{{ \Illuminate\Support\Str::limit($user->address, 60) }}</div>
</div>
@endif
</div>
<div class="user-card-actions">
<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> Edit
</a>
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
style="flex:0;"
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>
</div>
@endforeach
</div>
<!-- Pagination --> <!-- Pagination -->
<div class="d-flex justify-content-between align-items-center mt-4"> <div class="table-footer">
<small class="text-muted"> <small style="color:#6b7280;">
Menampilkan {{ $users->firstItem() }} sampai {{ $users->lastItem() }} Menampilkan {{ $users->firstItem() }}{{ $users->lastItem() }}
dari {{ $users->total() }} pengguna dari {{ $users->total() }} pengguna
</small> </small>
<div>{{ $users->links() }}</div> <div>{{ $users->links() }}</div>
</div> </div>
@else @else
<div class="text-center py-5"> <div style="text-align:center; padding:50px 20px; color:#9ca3af;">
<i class="fas fa-users fa-3x text-muted mb-3 d-block"></i> <i class="fas fa-users" style="font-size:2.5rem; display:block; margin-bottom:12px; opacity:0.3;"></i>
<h5 class="text-muted">Belum ada pengguna terdaftar</h5> <h5 style="color:#6b7280;">Belum ada pengguna terdaftar</h5>
<p class="text-muted">Mulai dengan menambahkan pengguna baru.</p> <p style="font-size:0.875rem;">Mulai dengan menambahkan pengguna baru.</p>
</div> </div>
@endif @endif
</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> <script>
// Live search // ===== SIDEBAR =====
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.toggle('open');
overlay.classList.toggle('show');
document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
}
function closeSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.remove('open');
overlay.classList.remove('show');
document.body.style.overflow = '';
}
window.addEventListener('resize', function() {
if (window.innerWidth > 900) closeSidebar();
});
// ===== LIVE SEARCH (desktop table + mobile cards) =====
document.getElementById('searchInput').addEventListener('keyup', function() { document.getElementById('searchInput').addEventListener('keyup', function() {
const filter = this.value.toLowerCase(); const filter = this.value.toLowerCase();
// Desktop table rows
const rows = document.querySelectorAll('#usersTable tbody tr'); const rows = document.querySelectorAll('#usersTable tbody tr');
rows.forEach(row => { rows.forEach(row => {
const text = row.textContent.toLowerCase(); row.style.display = row.textContent.toLowerCase().includes(filter) ? '' : 'none';
row.style.display = text.includes(filter) ? '' : 'none'; });
// Mobile cards
const cards = document.querySelectorAll('.user-card');
cards.forEach(card => {
const text = card.getAttribute('data-search') || card.textContent.toLowerCase();
card.style.display = text.includes(filter) ? '' : 'none';
}); });
}); });
</script> </script>

View File

@ -4,223 +4,765 @@
<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">
<title>Edit Pengguna - Bibit Cabai Admin</title> <title>Edit Pengguna - 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 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"> <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 { font-family:'Segoe UI',sans-serif; background:#f8f9fa; }
.admin-container { display:flex; min-height:100vh; } :root {
--sidebar-width: 260px;
--green-dark: #15803d;
--green-main: #16a34a;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f1f5f2;
overflow-x: hidden;
}
/* ===== SIDEBAR ===== */
.sidebar { .sidebar {
width:280px; background:linear-gradient(180deg,#28a745,#20c997); width: var(--sidebar-width);
color:white; min-height:100vh; position:fixed; top:0; left:0; z-index:1000; background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
color: white;
min-height: 100vh;
position: fixed;
top: 0; left: 0;
z-index: 1050;
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
} }
.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 {
.sidebar-menu { padding:20px 0; } padding: 24px 20px 20px;
text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
.sidebar-header .brand-icon { font-size: 2rem; margin-bottom: 6px; display: block; }
.sidebar-header h3 { font-size: 1.35rem; font-weight: 700; margin-bottom: 2px; }
.sidebar-header .subtitle { opacity: 0.65; font-size: 0.78rem; margin-bottom: 6px; }
.sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.sidebar-menu { padding: 12px 0; flex: 1; }
.menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item { .menu-item {
display:block; padding:15px 25px; color:white; text-decoration:none; display: flex;
transition:all 0.3s; border-left:4px solid transparent; align-items: center;
gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none;
transition: all 0.2s;
border-left: 3px solid transparent;
font-size: 0.9rem;
} }
.menu-item:hover, .menu-item.active {
background:rgba(255,255,255,0.2); color:white; .menu-item i { width: 18px; font-size: 0.95rem; flex-shrink: 0; text-align: center; }
border-left-color:#fff; transform:translateX(5px);
.menu-item:hover {
background: rgba(255,255,255,0.1);
color: white;
text-decoration: none;
border-left-color: rgba(255,255,255,0.5);
} }
.menu-item i { width:20px; margin-right:10px; }
.main-content { margin-left:280px; flex:1; } .menu-item.active {
.admin-header { background:white; padding:20px 30px; box-shadow:0 2px 4px rgba(0,0,0,0.1); } background: rgba(255,255,255,0.15);
.content-area { padding:30px; } color: white;
.form-card { background:white; border-radius:12px; padding:35px; box-shadow:0 4px 6px rgba(0,0,0,0.05); max-width:700px; } border-left-color: #fff;
.form-label { font-weight:600; color:#495057; } font-weight: 600;
.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; } .sidebar-footer {
.user-info-badge { padding: 16px 20px;
background:#f8f9fa; border-radius:8px; padding:12px 15px; border-top: 1px solid rgba(255,255,255,0.12);
margin-bottom:20px; font-size:0.85rem; color:#6c757d; flex-shrink: 0;
}
.btn-logout {
width: 100%;
background: rgba(255,255,255,0.12);
color: white;
border: 1.5px solid rgba(255,255,255,0.3);
padding: 9px 16px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.5);
color: white;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content {
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== TOPBAR ===== */
.topbar {
background: white;
height: 64px;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 0 #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.topbar-left { display: flex; align-items: center; gap: 14px; }
.hamburger-btn {
display: none;
background: none;
border: none;
color: #374151;
font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
.hamburger-btn:hover { background: #f3f4f6; }
.topbar-breadcrumb { font-size: 0.8rem; color: #6b7280; margin-bottom: 2px; }
.topbar-breadcrumb a { color: var(--green-main); text-decoration: none; }
.topbar-breadcrumb a:hover { text-decoration: underline; }
.topbar-title h2 { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.topbar-title p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.topbar-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.btn-topbar {
border: none;
padding: 8px 14px;
border-radius: 8px;
font-weight: 600;
font-size: 0.8rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
white-space: nowrap;
cursor: pointer;
}
.btn-topbar:hover { transform: translateY(-1px); text-decoration: none; }
.btn-topbar-back {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
}
.btn-topbar-back:hover { box-shadow: 0 4px 12px rgba(220,38,38,0.35); color: white; }
.btn-topbar-save {
background: linear-gradient(135deg, var(--green-main), var(--green-dark));
color: white;
}
.btn-topbar-save:hover { box-shadow: 0 4px 12px rgba(22,163,74,0.35); color: white; }
.btn-topbar-view {
background: linear-gradient(135deg, #0891b2, #0e7490);
color: white;
}
.btn-topbar-view:hover { box-shadow: 0 4px 12px rgba(8,145,178,0.35); color: white; }
/* ===== CONTENT AREA ===== */
.content-area { padding: 20px 24px 32px; flex: 1; }
/* ===== INFO CARD ===== */
.info-card {
background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
overflow: hidden;
margin-bottom: 16px;
}
.info-card-header {
padding: 16px 20px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
gap: 10px;
}
.info-card-icon {
width: 34px; height: 34px;
border-radius: 9px;
display: flex; align-items: center; justify-content: center;
font-size: 0.9rem;
flex-shrink: 0;
}
.icon-blue { background: #eff6ff; color: #2563eb; }
.icon-green { background: #f0fdf4; color: var(--green-main); }
.icon-orange { background: #fff7ed; color: #ea580c; }
.icon-purple { background: #faf5ff; color: #7c3aed; }
.icon-yellow { background: #fefce8; color: #ca8a04; }
.icon-gray { background: #f3f4f6; color: #6b7280; }
.icon-red { background: #fef2f2; color: #dc2626; }
.info-card-header h3 { font-size: 0.9rem; font-weight: 700; color: #111827; margin: 0; }
.info-card-header p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.info-card-body { padding: 20px; }
/* ===== SECTION DIVIDER ===== */
.section-divider {
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #9ca3af;
padding-bottom: 10px;
border-bottom: 1px solid #f3f4f6;
margin-bottom: 16px;
}
/* ===== FORM STYLES ===== */
.form-label-custom {
font-size: 0.78rem;
font-weight: 600;
color: #374151;
margin-bottom: 5px;
display: block;
}
.form-control-custom,
.form-select-custom {
font-size: 0.84rem;
padding: 8px 12px;
border: 1.5px solid #e5e7eb;
border-radius: 8px;
width: 100%;
color: #111827;
background: white;
transition: border-color 0.2s, box-shadow 0.2s;
outline: none;
}
.form-control-custom:focus,
.form-select-custom:focus {
border-color: var(--green-main);
box-shadow: 0 0 0 3px rgba(22,163,74,0.12);
}
.form-control-custom.is-invalid,
.form-select-custom.is-invalid {
border-color: #dc2626;
}
textarea.form-control-custom { resize: vertical; min-height: 90px; }
.invalid-feedback-custom {
font-size: 0.75rem;
color: #dc2626;
margin-top: 4px;
}
/* Password toggle */
.input-pw-wrap {
position: relative;
}
.input-pw-wrap .form-control-custom {
padding-right: 40px;
}
.btn-pw-toggle {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #9ca3af;
cursor: pointer;
font-size: 0.85rem;
padding: 0;
line-height: 1;
}
.btn-pw-toggle:hover { color: #374151; }
/* User info badge */
.user-meta-badge {
display: flex;
flex-wrap: wrap;
gap: 10px;
background: #f9fafb;
border: 1px solid #f3f4f6;
border-radius: 10px;
padding: 12px 16px;
margin-bottom: 20px;
font-size: 0.8rem;
color: #6b7280;
}
.user-meta-badge span { display: flex; align-items: center; gap: 5px; }
.user-meta-badge strong { color: #111827; }
/* Checkbox custom */
.check-card {
border: 1.5px solid #e5e7eb;
border-radius: 10px;
padding: 14px 16px;
display: flex;
align-items: flex-start;
gap: 12px;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
}
.check-card:has(input:checked) {
border-color: var(--green-main);
background: #f0fdf4;
}
.check-card input[type="checkbox"] {
width: 17px; height: 17px;
accent-color: var(--green-main);
margin-top: 2px;
flex-shrink: 0;
cursor: pointer;
}
.check-card-label { font-size: 0.84rem; font-weight: 600; color: #111827; }
.check-card-desc { font-size: 0.75rem; color: #6b7280; margin-top: 2px; }
.check-card-desc.text-danger { color: #dc2626 !important; }
/* ===== AVATAR MINI ===== */
.avatar-mini {
width: 44px; height: 44px;
border-radius: 50%;
background: linear-gradient(135deg, var(--green-main), var(--green-dark));
display: flex; align-items: center; justify-content: center;
font-size: 1.1rem; color: white; font-weight: 700;
flex-shrink: 0;
}
/* ===== FADE IN ===== */
.fade-in { animation: fadeIn 0.3s ease; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ===== RESPONSIVE ===== */
@media (max-width: 900px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.2); }
.main-content { margin-left: 0; }
.hamburger-btn { display: flex; align-items: center; justify-content: center; }
}
@media (max-width: 767px) {
.content-area { padding: 14px 14px 24px; }
.topbar { padding: 0 14px; height: auto; min-height: 58px; flex-wrap: wrap; gap: 8px; padding-top: 10px; padding-bottom: 10px; }
.topbar-actions .btn-topbar span { display: none; }
.topbar-actions .btn-topbar { padding: 8px 10px; }
} }
</style> </style>
</head> </head>
<body> <body>
<a href="{{ route('home') }}" class="back-button"> <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
</a>
<div class="admin-container"> <!-- ===== SIDEBAR ===== -->
<nav class="sidebar"> <nav class="sidebar" id="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<h3>🌱 Bibit Cabai</h3> <span class="brand-icon">🌱</span>
<p>Admin Dashboard</p> <h3>Bibit Cabai</h3>
<small class="text-light">{{ Auth::user()->name ?? 'Admin' }}</small> <p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div> </div>
<div class="sidebar-menu"> <div class="sidebar-menu">
<a href="{{ route('admin.dashboard') }}" class="menu-item"><i class="fas fa-tachometer-alt"></i>Dashboard</a> <div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.products.index') }}" class="menu-item"><i class="fas fa-seedling"></i>Kelola Produk</a> <a href="{{ route('admin.dashboard') }}" class="menu-item">
<a href="{{ route('admin.orders') }}" class="menu-item"><i class="fas fa-shopping-cart"></i>Pesanan</a> <i class="fas fa-tachometer-alt"></i><span>Dashboard</span>
<a href="{{ route('admin.users') }}" class="menu-item active"><i class="fas fa-users"></i>Pengguna</a> </a>
<a href="{{ route('admin.reports') }}" class="menu-item"><i class="fas fa-chart-line"></i>Laporan</a> <a href="{{ route('admin.products.index') }}" class="menu-item">
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"><i class="fas fa-cog"></i>Pengaturan</a> --> <i class="fas fa-seedling"></i><span>Kelola Produk</span>
<div class="mt-4 px-3"> </a>
<a href="{{ route('admin.orders') }}" class="menu-item">
<i class="fas fa-shopping-cart"></i><span>Pesanan</span>
</a>
<a href="{{ route('admin.cancellations') }}" class="menu-item">
<i class="fas fa-times-circle"></i><span>Pengajuan Batal</span>
</a>
<a href="{{ route('admin.users') }}" class="menu-item active">
<i class="fas fa-users"></i><span>Pengguna</span>
</a>
<a href="{{ route('admin.laporan') }}" class="menu-item">
<i class="fas fa-chart-line"></i><span>Laporan</span>
</a>
</div>
<div class="sidebar-footer">
<form method="POST" action="{{ route('admin.logout') }}"> <form method="POST" action="{{ route('admin.logout') }}">
@csrf @csrf
<button type="submit" class="btn btn-outline-light btn-sm w-100"> <button type="submit" class="btn-logout">
<i class="fas fa-sign-out-alt me-2"></i>Logout <i class="fas fa-sign-out-alt"></i><span>Logout</span>
</button> </button>
</form> </form>
</div> </div>
</div>
</nav> </nav>
<!-- ===== MAIN ===== -->
<div class="main-content"> <div class="main-content">
<div class="admin-header">
<nav aria-label="breadcrumb"> <!-- Topbar -->
<ol class="breadcrumb"> <header class="topbar">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a></li> <div class="topbar-left">
<li class="breadcrumb-item"><a href="{{ route('admin.users') }}">Pengguna</a></li> <button class="hamburger-btn" onclick="toggleSidebar()">
<li class="breadcrumb-item active">Edit Pengguna</li> <i class="fas fa-bars"></i>
</ol> </button>
</nav> <div class="topbar-title">
<div class="topbar-breadcrumb">
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
&nbsp;/&nbsp;<a href="{{ route('admin.users') }}">Pengguna</a>
&nbsp;/&nbsp;<a href="{{ route('admin.users.show', $user) }}">{{ $user->name }}</a>
&nbsp;/&nbsp;Edit
</div>
<h2>Edit Pengguna</h2> <h2>Edit Pengguna</h2>
<p class="mb-0">Perbarui informasi pengguna <strong>{{ $user->name }}</strong>.</p> <p>{{ $user->name }}</p>
</div> </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> &nbsp;|&nbsp;
Terdaftar: <strong>{{ $user->created_at->format('d M Y') }}</strong>
@if($user->email_verified_at)
&nbsp;|&nbsp; <span class="text-success"><i class="fas fa-check-circle"></i> Email Terverifikasi</span>
@else
&nbsp;|&nbsp; <span class="text-muted"><i class="fas fa-clock"></i> Belum Verifikasi</span>
@endif
</div> </div>
<div class="topbar-actions">
<a href="{{ route('admin.users') }}" class="btn-topbar btn-topbar-back">
<i class="fas fa-arrow-left"></i>
<span>Kembali</span>
</a>
<a href="{{ route('admin.users.show', $user) }}" class="btn-topbar btn-topbar-view">
<i class="fas fa-eye"></i>
<span>Lihat Detail</span>
</a>
<button type="submit" form="editUserForm" class="btn-topbar btn-topbar-save">
<i class="fas fa-save"></i>
<span>Simpan</span>
</button>
</div>
</header>
@if($errors->any()) <!-- Content -->
<div class="alert alert-danger"> <div class="content-area fade-in">
<ul class="mb-0">
@foreach($errors->all() as $error) @if ($errors->any())
<li>{{ $error }}</li> <div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
<i class="fas fa-exclamation-circle me-2"></i><strong>Error!</strong>
<ul class="mb-0 mt-1">
@foreach ($errors->all() as $error)
<li style="font-size:0.85rem;">{{ $error }}</li>
@endforeach @endforeach
</ul> </ul>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div> </div>
@endif @endif
<form method="POST" action="{{ route('admin.users.update', $user) }}"> @if(session('success'))
<div class="alert alert-success alert-dismissible fade show mb-4" 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
<form id="editUserForm" method="POST" action="{{ route('admin.users.update', $user) }}">
@csrf @csrf
@method('PUT') @method('PUT')
<h5 class="mb-3 text-success"><i class="fas fa-user me-2"></i>Informasi Dasar</h5> <div class="row g-3">
<div class="mb-3"> <!-- ===== KOLOM KIRI (8/12) ===== -->
<label class="form-label">Nama Lengkap <span class="text-danger">*</span></label> <div class="col-lg-8">
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror"
<!-- Informasi Dasar -->
<div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-blue">
<i class="fas fa-user"></i>
</div>
<div>
<h3>Informasi Dasar</h3>
<p>Nama, email, dan password pengguna</p>
</div>
</div>
<div class="info-card-body">
<!-- User meta badge -->
<div class="user-meta-badge">
<span><i class="fas fa-hashtag" style="color:var(--green-main)"></i> ID: <strong>#{{ $user->id }}</strong></span>
<span><i class="fas fa-calendar" style="color:var(--green-main)"></i> Terdaftar: <strong>{{ $user->created_at->format('d M Y') }}</strong></span>
<span>
@if($user->email_verified_at)
<i class="fas fa-check-circle" style="color:#16a34a"></i> <strong style="color:#16a34a">Email Terverifikasi</strong>
@else
<i class="fas fa-clock" style="color:#9ca3af"></i> <span style="color:#9ca3af">Belum Verifikasi</span>
@endif
</span>
</div>
<div class="row g-3">
<div class="col-12">
<label class="form-label-custom">Nama Lengkap <span style="color:#dc2626">*</span></label>
<input type="text" name="name"
class="form-control-custom @error('name') is-invalid @enderror"
value="{{ old('name', $user->name) }}" required> value="{{ old('name', $user->name) }}" required>
@error('name')<div class="invalid-feedback">{{ $message }}</div>@enderror @error('name')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
</div> </div>
<div class="col-12">
<div class="mb-3"> <label class="form-label-custom">Email <span style="color:#dc2626">*</span></label>
<label class="form-label">Email <span class="text-danger">*</span></label> <input type="email" name="email"
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror" class="form-control-custom @error('email') is-invalid @enderror"
value="{{ old('email', $user->email) }}" required> value="{{ old('email', $user->email) }}" required>
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror @error('email')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
</div>
</div> </div>
<div class="row"> <div class="section-divider mt-4">Ganti Password</div>
<div class="col-md-6 mb-3"> <p style="font-size:0.78rem;color:#9ca3af;margin-bottom:14px;">Kosongkan jika tidak ingin mengganti password.</p>
<label class="form-label">Password Baru</label>
<div class="input-group"> <div class="row g-3">
<div class="col-md-6">
<label class="form-label-custom">Password Baru</label>
<div class="input-pw-wrap">
<input type="password" name="password" id="password" <input type="password" name="password" id="password"
class="form-control @error('password') is-invalid @enderror" class="form-control-custom @error('password') is-invalid @enderror"
placeholder="Minimal 8 karakter"> placeholder="Minimal 8 karakter">
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('password', 'eye1')"> <button type="button" class="btn-pw-toggle" onclick="togglePassword('password','eye1')">
<i class="fas fa-eye" id="eye1"></i> <i class="fas fa-eye" id="eye1"></i>
</button> </button>
</div> </div>
@error('password')<div class="text-danger small mt-1">{{ $message }}</div>@enderror @error('password')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6">
<label class="form-label">Konfirmasi Password Baru</label> <label class="form-label-custom">Konfirmasi Password Baru</label>
<div class="input-group"> <div class="input-pw-wrap">
<input type="password" name="password_confirmation" id="password_confirmation" <input type="password" name="password_confirmation" id="password_confirmation"
class="form-control" class="form-control-custom"
placeholder="Ulangi password baru"> placeholder="Ulangi password baru">
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword('password_confirmation', 'eye2')"> <button type="button" class="btn-pw-toggle" onclick="togglePassword('password_confirmation','eye2')">
<i class="fas fa-eye" id="eye2"></i> <i class="fas fa-eye" id="eye2"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="section-divider"></div> </div>
<h5 class="mb-3 text-success"><i class="fas fa-address-card me-2"></i>Informasi Tambahan</h5> </div>
<div class="mb-3"> <!-- Informasi Tambahan -->
<label class="form-label">Nomor Telepon</label> <div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-orange">
<i class="fas fa-address-card"></i>
</div>
<div>
<h3>Informasi Tambahan</h3>
<p>Telepon dan alamat pengguna</p>
</div>
</div>
<div class="info-card-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label-custom">Nomor Telepon</label>
<input type="text" name="phone" <input type="text" name="phone"
value="{{ old('phone', '+62') }}" class="form-control-custom @error('phone') is-invalid @enderror"
value="{{ old('phone', $user->phone ?? '+62') }}"
placeholder="+62812xxxxxxxx"> placeholder="+62812xxxxxxxx">
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror @error('phone')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
</div>
<div class="col-12">
<label class="form-label-custom">Alamat</label>
<textarea name="address"
class="form-control-custom @error('address') is-invalid @enderror"
rows="4">{{ old('address', $user->address) }}</textarea>
@error('address')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
</div>
</div>
</div>
</div> </div>
<div class="mb-3"> <!-- Hak Akses -->
<label class="form-label">Alamat</label> <div class="info-card">
<textarea name="address" class="form-control @error('address') is-invalid @enderror" <div class="info-card-header">
rows="4" style="min-height: 100px; resize: vertical;">{{ old('address', $user->address) }}</textarea> <div class="info-card-icon icon-purple">
@error('address')<div class="invalid-feedback">{{ $message }}</div>@enderror <i class="fas fa-shield-alt"></i>
</div> </div>
<div>
<div class="section-divider"></div> <h3>Hak Akses</h3>
<h5 class="mb-3 text-success"><i class="fas fa-shield-alt me-2"></i>Hak Akses</h5> <p>Role dan izin pengguna</p>
</div>
<div class="mb-4"> </div>
<div class="form-check"> <div class="info-card-body">
<input class="form-check-input" type="checkbox" name="is_admin" id="is_admin" <label class="check-card" for="is_admin">
<input class="form-check-input" type="checkbox"
name="is_admin" id="is_admin"
{{ old('is_admin', $user->is_admin) ? 'checked' : '' }} {{ old('is_admin', $user->is_admin) ? 'checked' : '' }}
{{ $user->id === auth()->id() ? 'disabled' : '' }}> {{ $user->id === auth()->id() ? 'disabled' : '' }}>
<label class="form-check-label" for="is_admin"> <div>
<strong>Jadikan Admin</strong> <div class="check-card-label"><i class="fas fa-shield-alt me-1" style="color:var(--green-main)"></i> Jadikan Admin</div>
@if($user->id === auth()->id()) @if($user->id === auth()->id())
<small class="text-danger d-block">Anda tidak dapat mengubah role akun sendiri.</small> <div class="check-card-desc text-danger">Anda tidak dapat mengubah role akun sendiri.</div>
@else @else
<small class="text-muted d-block">Admin memiliki akses penuh ke dashboard.</small> <div class="check-card-desc">Admin memiliki akses penuh ke seluruh dashboard.</div>
@endif @endif
</div>
</label> </label>
</div> </div>
</div> </div>
<div class="d-flex gap-2"> </div>
<button type="submit" class="btn btn-save"> <!-- Akhir col-lg-8 -->
<i class="fas fa-save me-2"></i>Simpan Perubahan
<!-- ===== KOLOM KANAN (4/12) ===== -->
<div class="col-lg-4">
<!-- User Profile Card -->
<div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-green">
<i class="fas fa-id-card"></i>
</div>
<div>
<h3>Profil Pengguna</h3>
<p>Ringkasan akun</p>
</div>
</div>
<div class="info-card-body" style="text-align:center;">
<div style="width:64px;height:64px;border-radius:50%;
background:linear-gradient(135deg,var(--green-main),var(--green-dark));
display:flex;align-items:center;justify-content:center;
font-size:1.5rem;color:white;font-weight:700;
margin:0 auto 12px;">
{{ strtoupper(substr($user->name, 0, 1)) }}
</div>
<div style="font-weight:700;color:#111827;font-size:0.95rem;margin-bottom:4px;">{{ $user->name }}</div>
<div style="font-size:0.78rem;color:#6b7280;margin-bottom:12px;">{{ $user->email }}</div>
<div style="display:inline-flex;align-items:center;gap:6px;
padding:4px 12px;border-radius:20px;font-size:0.75rem;font-weight:700;
{{ $user->is_admin ? 'background:#dbeafe;color:#1e40af;' : 'background:#dcfce7;color:#14532d;' }}">
<i class="fas {{ $user->is_admin ? 'fa-shield-alt' : 'fa-user' }}"></i>
{{ $user->is_admin ? 'Admin' : 'User' }}
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-gray">
<i class="fas fa-cog"></i>
</div>
<div>
<h3>Aksi</h3>
<p>Simpan atau batalkan perubahan</p>
</div>
</div>
<div class="info-card-body" style="display:flex;flex-direction:column;gap:10px;">
<button type="submit" form="editUserForm"
style="width:100%;padding:10px 16px;border-radius:9px;border:none;
background:linear-gradient(135deg,var(--green-main),var(--green-dark));
color:white;font-weight:700;font-size:0.875rem;cursor:pointer;
display:flex;align-items:center;justify-content:center;gap:8px;
transition:all 0.2s;">
<i class="fas fa-save"></i> Simpan Perubahan
</button> </button>
<a href="{{ route('admin.users') }}" class="btn btn-outline-secondary"> <a href="{{ route('admin.users.show', $user) }}"
<i class="fas fa-times me-2"></i>Batal style="width:100%;padding:10px 16px;border-radius:9px;
border:1.5px solid #bfdbfe;background:#eff6ff;
color:#2563eb;font-weight:600;font-size:0.875rem;
display:flex;align-items:center;justify-content:center;gap:8px;
text-decoration:none;transition:all 0.2s;">
<i class="fas fa-eye"></i> Lihat Detail
</a> </a>
<a href="{{ route('admin.users.show', $user) }}" class="btn btn-outline-info ms-auto"> <a href="{{ route('admin.users') }}"
<i class="fas fa-eye me-2"></i>Lihat Detail style="width:100%;padding:10px 16px;border-radius:9px;
border:1.5px solid #e5e7eb;background:#f9fafb;
color:#374151;font-weight:600;font-size:0.875rem;
display:flex;align-items:center;justify-content:center;gap:8px;
text-decoration:none;transition:all 0.2s;">
<i class="fas fa-times"></i> Batal
</a> </a>
</div> </div>
</div>
</div>
<!-- Akhir col-lg-4 -->
</div>
</form> </form>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </div>
<script> </div>
function togglePassword(fieldId, iconId) {
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
document.getElementById('sidebarOverlay').classList.toggle('show');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('sidebarOverlay').classList.remove('show');
}
window.addEventListener('resize', function() {
if (window.innerWidth > 900) closeSidebar();
});
function togglePassword(fieldId, iconId) {
const field = document.getElementById(fieldId); const field = document.getElementById(fieldId);
const icon = document.getElementById(iconId); const icon = document.getElementById(iconId);
if (field.type === 'password') { if (field.type === 'password') {
@ -230,7 +772,16 @@ function togglePassword(fieldId, iconId) {
field.type = 'password'; field.type = 'password';
icon.classList.replace('fa-eye-slash', 'fa-eye'); icon.classList.replace('fa-eye-slash', 'fa-eye');
} }
} }
</script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.alert').forEach(function(alert) {
setTimeout(function() {
const a = bootstrap.Alert.getOrCreateInstance(alert);
if (a) a.close();
}, 5000);
});
});
</script>
</body> </body>
</html> </html>

View File

@ -4,180 +4,679 @@
<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">
<title>Detail Pengguna - Bibit Cabai Admin</title> <title>Detail Pengguna - 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 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"> <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 { font-family:'Segoe UI',sans-serif; background:#f8f9fa; }
.admin-container { display:flex; min-height:100vh; } :root {
--sidebar-width: 260px;
--green-dark: #15803d;
--green-main: #16a34a;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f1f5f2;
overflow-x: hidden;
}
/* ===== SIDEBAR ===== */
.sidebar { .sidebar {
width:280px; background:linear-gradient(180deg,#28a745,#20c997); width: var(--sidebar-width);
color:white; min-height:100vh; position:fixed; top:0; left:0; z-index:1000; background: linear-gradient(180deg, var(--green-dark) 0%, #14532d 100%);
color: white;
min-height: 100vh;
position: fixed;
top: 0; left: 0;
z-index: 1050;
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
overflow-y: auto;
} }
.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 {
.sidebar-menu { padding:20px 0; } padding: 24px 20px 20px;
text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.12);
flex-shrink: 0;
}
.sidebar-header .brand-icon { font-size: 2rem; margin-bottom: 6px; display: block; }
.sidebar-header h3 { font-size: 1.35rem; font-weight: 700; margin-bottom: 2px; }
.sidebar-header .subtitle { opacity: 0.65; font-size: 0.78rem; margin-bottom: 6px; }
.sidebar-header .admin-name {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 3px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.sidebar-menu { padding: 12px 0; flex: 1; }
.menu-section-label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.45;
padding: 12px 20px 6px;
}
.menu-item { .menu-item {
display:block; padding:15px 25px; color:white; text-decoration:none; display: flex;
transition:all 0.3s; border-left:4px solid transparent; align-items: center;
gap: 12px;
padding: 12px 20px;
color: rgba(255,255,255,0.82);
text-decoration: none;
transition: all 0.2s;
border-left: 3px solid transparent;
font-size: 0.9rem;
} }
.menu-item:hover, .menu-item.active {
background:rgba(255,255,255,0.2); color:white; .menu-item i { width: 18px; font-size: 0.95rem; flex-shrink: 0; text-align: center; }
border-left-color:#fff; transform:translateX(5px);
.menu-item:hover {
background: rgba(255,255,255,0.1);
color: white;
text-decoration: none;
border-left-color: rgba(255,255,255,0.5);
} }
.menu-item i { width:20px; margin-right:10px; }
.main-content { margin-left:280px; flex:1; } .menu-item.active {
.admin-header { background:white; padding:20px 30px; box-shadow:0 2px 4px rgba(0,0,0,0.1); } background: rgba(255,255,255,0.15);
.content-area { padding:30px; } color: white;
.detail-card { background:white; border-radius:12px; padding:35px; box-shadow:0 4px 6px rgba(0,0,0,0.05); max-width:700px; } border-left-color: #fff;
.avatar-circle { font-weight: 600;
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; } .sidebar-footer {
.info-label { width:160px; font-weight:600; color:#6c757d; font-size:0.9rem; flex-shrink:0; } padding: 16px 20px;
.info-value { color:#333; flex:1; } border-top: 1px solid rgba(255,255,255,0.12);
.badge-role { padding:5px 12px; border-radius:12px; font-size:0.8rem; font-weight:600; } flex-shrink: 0;
.badge-admin { background:#cce5ff; color:#004085; } }
.badge-user { background:#d4edda; color:#155724; }
.badge-verified { background:#d4edda; color:#155724; } .btn-logout {
.badge-unverified { background:#e2e3e5; color:#383d41; } width: 100%;
.breadcrumb { background:transparent; padding:0; margin-bottom:10px; } background: rgba(255,255,255,0.12);
.breadcrumb-item a { color:#28a745; text-decoration:none; } color: white;
.back-button { border: 1.5px solid rgba(255,255,255,0.3);
position:fixed; top:20px; right:20px; z-index:1100; padding: 9px 16px;
background:linear-gradient(45deg,#dc3545,#c82333); border-radius: 8px;
color:white; border:none; padding:12px 20px; font-size: 0.875rem;
border-radius:25px; font-weight:600; text-decoration:none; transition:all 0.3s; font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.5);
color: white;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 1040;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show { display: block; }
/* ===== MAIN CONTENT ===== */
.main-content {
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== TOPBAR ===== */
.topbar {
background: white;
height: 64px;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 0 #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.topbar-left { display: flex; align-items: center; gap: 14px; }
.hamburger-btn {
display: none;
background: none;
border: none;
color: #374151;
font-size: 1.25rem;
padding: 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
.hamburger-btn:hover { background: #f3f4f6; }
.topbar-breadcrumb { font-size: 0.8rem; color: #6b7280; margin-bottom: 2px; }
.topbar-breadcrumb a { color: var(--green-main); text-decoration: none; }
.topbar-breadcrumb a:hover { text-decoration: underline; }
.topbar-title h2 { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.topbar-title p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.topbar-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.btn-topbar {
border: none;
padding: 8px 14px;
border-radius: 8px;
font-weight: 600;
font-size: 0.8rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
white-space: nowrap;
cursor: pointer;
}
.btn-topbar:hover { transform: translateY(-1px); text-decoration: none; }
.btn-topbar-back {
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
}
.btn-topbar-back:hover { box-shadow: 0 4px 12px rgba(220,38,38,0.35); color: white; }
.btn-topbar-edit {
background: linear-gradient(135deg, #ca8a04, #a16207);
color: white;
}
.btn-topbar-edit:hover { box-shadow: 0 4px 12px rgba(202,138,4,0.35); color: white; }
.btn-topbar-delete {
background: #fef2f2;
color: #dc2626;
border: 1.5px solid #fecaca;
}
.btn-topbar-delete:hover { background: #fee2e2; color: #dc2626; }
/* ===== CONTENT AREA ===== */
.content-area { padding: 20px 24px 32px; flex: 1; }
/* ===== INFO CARD ===== */
.info-card {
background: white;
border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 10px rgba(0,0,0,0.04);
overflow: hidden;
margin-bottom: 16px;
}
.info-card-header {
padding: 16px 20px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
gap: 10px;
}
.info-card-icon {
width: 34px; height: 34px;
border-radius: 9px;
display: flex; align-items: center; justify-content: center;
font-size: 0.9rem;
flex-shrink: 0;
}
.icon-blue { background: #eff6ff; color: #2563eb; }
.icon-green { background: #f0fdf4; color: var(--green-main); }
.icon-orange { background: #fff7ed; color: #ea580c; }
.icon-purple { background: #faf5ff; color: #7c3aed; }
.icon-yellow { background: #fefce8; color: #ca8a04; }
.icon-gray { background: #f3f4f6; color: #6b7280; }
.info-card-header h3 { font-size: 0.9rem; font-weight: 700; color: #111827; margin: 0; }
.info-card-header p { font-size: 0.75rem; color: #6b7280; margin: 0; }
.info-card-body { padding: 20px; }
/* ===== DETAIL ROW ===== */
.detail-row {
display: flex;
padding: 8px 0;
border-bottom: 1px solid #f9fafb;
font-size: 0.84rem;
gap: 12px;
}
.detail-row:last-child { border-bottom: none; }
.detail-label {
width: 140px;
flex-shrink: 0;
color: #6b7280;
font-weight: 500;
}
.detail-value { color: #111827; flex: 1; }
/* ===== BADGES ===== */
.badge-status {
padding: 4px 10px;
border-radius: 20px;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.badge-admin { background: #dbeafe; color: #1e40af; }
.badge-user { background: #dcfce7; color: #14532d; }
.badge-verified { background: #dcfce7; color: #14532d; }
.badge-unverified { background: #f3f4f6; color: #6b7280; }
/* ===== AVATAR ===== */
.avatar-lg {
width: 72px; height: 72px;
border-radius: 50%;
background: linear-gradient(135deg, var(--green-main), var(--green-dark));
display: flex; align-items: center; justify-content: center;
font-size: 1.75rem; color: white; font-weight: 700;
flex-shrink: 0;
}
/* ===== FADE IN ===== */
.fade-in { animation: fadeIn 0.3s ease; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ===== RESPONSIVE ===== */
@media (max-width: 900px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.2); }
.main-content { margin-left: 0; }
.hamburger-btn { display: flex; align-items: center; justify-content: center; }
}
@media (max-width: 767px) {
.content-area { padding: 14px 14px 24px; }
.topbar { padding: 0 14px; height: auto; min-height: 58px; flex-wrap: wrap; gap: 8px; padding-top: 10px; padding-bottom: 10px; }
.topbar-actions .btn-topbar span { display: none; }
.topbar-actions .btn-topbar { padding: 8px 10px; }
.detail-label { width: 110px; }
} }
.back-button:hover { transform:translateY(-2px); color:white; }
</style> </style>
</head> </head>
<body> <body>
<a href="{{ route('home') }}" class="back-button"> <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<i class="fas fa-arrow-left me-2"></i>Kembali ke Website
</a>
<div class="admin-container"> <!-- ===== SIDEBAR ===== -->
<nav class="sidebar"> <nav class="sidebar" id="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<h3>🌱 Bibit Cabai</h3> <span class="brand-icon">🌱</span>
<p>Admin Dashboard</p> <h3>Bibit Cabai</h3>
<small class="text-light">{{ Auth::user()->name ?? 'Admin' }}</small> <p class="subtitle">Admin Dashboard</p>
<span class="admin-name">{{ Auth::user()->name ?? 'Admin User' }}</span>
</div> </div>
<div class="sidebar-menu"> <div class="sidebar-menu">
<a href="{{ route('admin.dashboard') }}" class="menu-item"><i class="fas fa-tachometer-alt"></i>Dashboard</a> <div class="menu-section-label">Menu Utama</div>
<a href="{{ route('admin.products.index') }}" class="menu-item"><i class="fas fa-seedling"></i>Kelola Produk</a> <a href="{{ route('admin.dashboard') }}" class="menu-item">
<a href="{{ route('admin.orders') }}" class="menu-item"><i class="fas fa-shopping-cart"></i>Pesanan</a> <i class="fas fa-tachometer-alt"></i><span>Dashboard</span>
<a href="{{ route('admin.users') }}" class="menu-item active"><i class="fas fa-users"></i>Pengguna</a> </a>
<a href="{{ route('admin.reports') }}" class="menu-item"><i class="fas fa-chart-line"></i>Laporan</a> <a href="{{ route('admin.products.index') }}" class="menu-item">
<!-- <a href="{{ route('admin.settings') }}" class="menu-item"><i class="fas fa-cog"></i>Pengaturan</a> --> <i class="fas fa-seedling"></i><span>Kelola Produk</span>
<div class="mt-4 px-3"> </a>
<a href="{{ route('admin.orders') }}" class="menu-item">
<i class="fas fa-shopping-cart"></i><span>Pesanan</span>
</a>
<a href="{{ route('admin.cancellations') }}" class="menu-item">
<i class="fas fa-times-circle"></i><span>Pengajuan Batal</span>
</a>
<a href="{{ route('admin.users') }}" class="menu-item active">
<i class="fas fa-users"></i><span>Pengguna</span>
</a>
<a href="{{ route('admin.laporan') }}" class="menu-item">
<i class="fas fa-chart-line"></i><span>Laporan</span>
</a>
</div>
<div class="sidebar-footer">
<form method="POST" action="{{ route('admin.logout') }}"> <form method="POST" action="{{ route('admin.logout') }}">
@csrf @csrf
<button type="submit" class="btn btn-outline-light btn-sm w-100"> <button type="submit" class="btn-logout">
<i class="fas fa-sign-out-alt me-2"></i>Logout <i class="fas fa-sign-out-alt"></i><span>Logout</span>
</button> </button>
</form> </form>
</div> </div>
</div>
</nav> </nav>
<!-- ===== MAIN ===== -->
<div class="main-content"> <div class="main-content">
<div class="admin-header">
<nav aria-label="breadcrumb"> <!-- Topbar -->
<ol class="breadcrumb"> <header class="topbar">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a></li> <div class="topbar-left">
<li class="breadcrumb-item"><a href="{{ route('admin.users') }}">Pengguna</a></li> <button class="hamburger-btn" onclick="toggleSidebar()">
<li class="breadcrumb-item active">Detail Pengguna</li> <i class="fas fa-bars"></i>
</ol> </button>
</nav> <div class="topbar-title">
<div class="topbar-breadcrumb">
<a href="{{ route('admin.dashboard') }}"><i class="fas fa-home"></i> Dashboard</a>
&nbsp;/&nbsp;<a href="{{ route('admin.users') }}">Pengguna</a>
&nbsp;/&nbsp;{{ $user->name }}
</div>
<h2>Detail Pengguna</h2> <h2>Detail Pengguna</h2>
<p class="mb-0">Informasi lengkap pengguna.</p> <p>{{ $user->name }}</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> </div>
<div class="info-row"> <div class="topbar-actions">
<div class="info-label"><i class="fas fa-phone me-2 text-muted"></i>Telepon</div> <a href="{{ route('admin.users') }}" class="btn-topbar btn-topbar-back">
<div class="info-value">{{ $user->phone ?? '-' }}</div> <i class="fas fa-arrow-left"></i>
</div> <span>Kembali</span>
<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>
<a href="{{ route('admin.users') }}" class="btn btn-outline-secondary"> <a href="{{ route('admin.users.edit', $user) }}" class="btn-topbar btn-topbar-edit">
<i class="fas fa-arrow-left me-2"></i>Kembali <i class="fas fa-edit"></i>
<span>Edit</span>
</a> </a>
@if($user->id !== auth()->id()) @if($user->id !== auth()->id())
<form method="POST" action="{{ route('admin.users.destroy', $user) }}" <button type="button" class="btn-topbar btn-topbar-delete"
class="ms-auto" onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')"> data-bs-toggle="modal" data-bs-target="#deleteModal">
@csrf @method('DELETE') <i class="fas fa-trash"></i>
<button type="submit" class="btn btn-danger"> <span>Hapus</span>
<i class="fas fa-trash me-2"></i>Hapus
</button> </button>
</form> @endif
</div>
</header>
<!-- Content -->
<div class="content-area fade-in">
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show mb-4" 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
<div class="row g-3">
<!-- ===== KOLOM KIRI (8/12) ===== -->
<div class="col-lg-8">
<!-- Profil & Info Dasar -->
<div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-blue">
<i class="fas fa-user"></i>
</div>
<div style="flex:1;">
<h3>Informasi Pengguna</h3>
<p>Detail akun & status</p>
</div>
<span class="badge-status {{ $user->is_admin ? 'badge-admin' : 'badge-user' }}">
<i class="fas {{ $user->is_admin ? 'fa-shield-alt' : 'fa-user' }} me-1"></i>
{{ $user->is_admin ? 'Admin' : 'User' }}
</span>
</div>
<div class="info-card-body">
<!-- Avatar + nama -->
<div style="display:flex;align-items:center;gap:16px;margin-bottom:20px;
padding-bottom:16px;border-bottom:1px solid #f3f4f6;">
<div class="avatar-lg">{{ strtoupper(substr($user->name, 0, 1)) }}</div>
<div>
<div style="font-size:1.05rem;font-weight:700;color:#111827;">{{ $user->name }}</div>
<div style="font-size:0.82rem;color:#6b7280;margin-top:2px;">{{ $user->email }}</div>
<div style="margin-top:8px;">
@if($user->email_verified_at)
<span class="badge-status badge-verified">
<i class="fas fa-check me-1"></i>Email Terverifikasi
</span>
@else
<span class="badge-status badge-unverified">
<i class="fas fa-clock me-1"></i>Belum Verifikasi
</span>
@endif @endif
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <div class="row g-4">
<div class="col-md-6">
<div style="font-size:0.72rem;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:#9ca3af;padding-bottom:10px;border-bottom:1px solid #f3f4f6;margin-bottom:16px;">Akun</div>
<div class="detail-row">
<span class="detail-label"><i class="fas fa-hashtag me-1" style="font-size:0.7rem;"></i> ID</span>
<span class="detail-value"><strong>#{{ $user->id }}</strong></span>
</div>
<div class="detail-row">
<span class="detail-label"><i class="fas fa-envelope me-1" style="font-size:0.7rem;"></i> Email</span>
<span class="detail-value">{{ $user->email }}</span>
</div>
<div class="detail-row">
<span class="detail-label"><i class="fas fa-check-circle me-1" style="font-size:0.7rem;"></i> Verifikasi</span>
<span class="detail-value">
@if($user->email_verified_at)
<span style="color:#16a34a;font-size:0.8rem;">{{ $user->email_verified_at->format('d M Y') }}</span>
@else
<span style="color:#9ca3af;font-size:0.8rem;">Belum verifikasi</span>
@endif
</span>
</div>
<div class="detail-row">
<span class="detail-label"><i class="fas fa-shield-alt me-1" style="font-size:0.7rem;"></i> Role</span>
<span class="detail-value">
<span class="badge-status {{ $user->is_admin ? 'badge-admin' : 'badge-user' }}">
{{ $user->is_admin ? 'Admin' : 'User' }}
</span>
</span>
</div>
</div>
<div class="col-md-6">
<div style="font-size:0.72rem;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:#9ca3af;padding-bottom:10px;border-bottom:1px solid #f3f4f6;margin-bottom:16px;">Waktu</div>
<div class="detail-row">
<span class="detail-label"><i class="fas fa-calendar me-1" style="font-size:0.7rem;"></i> Bergabung</span>
<span class="detail-value">{{ $user->created_at->format('d M Y, H:i') }}</span>
</div>
<div class="detail-row">
<span class="detail-label"><i class="fas fa-sync me-1" style="font-size:0.7rem;"></i> Diperbarui</span>
<span class="detail-value">{{ $user->updated_at->format('d M Y, H:i') }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Informasi Kontak -->
<div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-orange">
<i class="fas fa-address-card"></i>
</div>
<div>
<h3>Kontak & Alamat</h3>
<p>Telepon dan alamat pengguna</p>
</div>
</div>
<div class="info-card-body">
<div class="detail-row">
<span class="detail-label"><i class="fas fa-phone me-1" style="font-size:0.7rem;color:var(--green-main)"></i> Telepon</span>
<span class="detail-value">{{ $user->phone ?? '-' }}</span>
</div>
<div class="detail-row">
<span class="detail-label"><i class="fas fa-map-marker-alt me-1" style="font-size:0.7rem;color:var(--green-main)"></i> Alamat</span>
<span class="detail-value" style="line-height:1.6;">{{ $user->address ?? '-' }}</span>
</div>
</div>
</div>
</div>
<!-- Akhir col-lg-8 -->
<!-- ===== KOLOM KANAN (4/12) ===== -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-yellow">
<i class="fas fa-bolt"></i>
</div>
<div>
<h3>Aksi Cepat</h3>
<p>Kelola pengguna ini</p>
</div>
</div>
<div class="info-card-body" style="display:flex;flex-direction:column;gap:10px;">
<a href="{{ route('admin.users.edit', $user) }}"
style="width:100%;padding:10px 16px;border-radius:9px;border:none;
background:linear-gradient(135deg,#ca8a04,#a16207);
color:white;font-weight:700;font-size:0.875rem;
display:flex;align-items:center;justify-content:center;gap:8px;
text-decoration:none;transition:all 0.2s;">
<i class="fas fa-edit"></i> Edit Pengguna
</a>
<a href="{{ route('admin.users') }}"
style="width:100%;padding:10px 16px;border-radius:9px;
border:1.5px solid #e5e7eb;background:#f9fafb;
color:#374151;font-weight:600;font-size:0.875rem;
display:flex;align-items:center;justify-content:center;gap:8px;
text-decoration:none;transition:all 0.2s;">
<i class="fas fa-arrow-left"></i> Kembali ke Daftar
</a>
@if($user->id !== auth()->id())
<button type="button"
data-bs-toggle="modal" data-bs-target="#deleteModal"
style="width:100%;padding:10px 16px;border-radius:9px;
border:1.5px solid #fecaca;background:#fef2f2;
color:#dc2626;font-weight:600;font-size:0.875rem;
display:flex;align-items:center;justify-content:center;gap:8px;
cursor:pointer;transition:all 0.2s;">
<i class="fas fa-trash"></i> Hapus Pengguna
</button>
@endif
</div>
</div>
<!-- Status Summary -->
<div class="info-card">
<div class="info-card-header">
<div class="info-card-icon icon-purple">
<i class="fas fa-info-circle"></i>
</div>
<div>
<h3>Ringkasan Status</h3>
<p>Status akun pengguna</p>
</div>
</div>
<div class="info-card-body">
<div class="detail-row">
<span class="detail-label">Role</span>
<span class="detail-value">
<span class="badge-status {{ $user->is_admin ? 'badge-admin' : 'badge-user' }}">
{{ $user->is_admin ? 'Admin' : 'User' }}
</span>
</span>
</div>
<div class="detail-row">
<span class="detail-label">Email</span>
<span class="detail-value">
@if($user->email_verified_at)
<span class="badge-status badge-verified">Terverifikasi</span>
@else
<span class="badge-status badge-unverified">Belum Verifikasi</span>
@endif
</span>
</div>
<div class="detail-row">
<span class="detail-label">ID Pengguna</span>
<span class="detail-value"><code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:0.8rem;">#{{ $user->id }}</code></span>
</div>
</div>
</div>
</div>
<!-- Akhir col-lg-4 -->
</div>
</div>
</div>
<!-- ===== DELETE MODAL ===== -->
@if($user->id !== auth()->id())
<div class="modal fade" id="deleteModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border:none;border-radius:14px;overflow:hidden;">
<div class="modal-header" style="background:#dc2626;color:white;border:none;">
<h5 class="modal-title">
<i class="fas fa-exclamation-triangle me-2"></i>Konfirmasi Hapus
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" style="padding:24px;">
<p style="color:#374151;margin-bottom:8px;">Apakah Anda yakin ingin menghapus pengguna ini?</p>
<p style="font-weight:700;color:#111827;margin-bottom:12px;">{{ $user->name }} ({{ $user->email }})</p>
<p style="color:#dc2626;font-size:0.84rem;margin:0;">
<i class="fas fa-exclamation-triangle me-1"></i> Tindakan ini tidak dapat dibatalkan!
</p>
</div>
<div class="modal-footer" style="border:none;padding:16px 24px;">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<form action="{{ route('admin.users.destroy', $user) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash me-1"></i> Hapus Pengguna
</button>
</form>
</div>
</div>
</div>
</div>
@endif
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
document.getElementById('sidebarOverlay').classList.toggle('show');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('sidebarOverlay').classList.remove('show');
}
window.addEventListener('resize', function() {
if (window.innerWidth > 900) closeSidebar();
});
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.alert').forEach(function(alert) {
setTimeout(function() {
const a = bootstrap.Alert.getOrCreateInstance(alert);
if (a) a.close();
}, 5000);
});
});
</script>
</body> </body>
</html> </html>

View File

@ -40,7 +40,6 @@
border-color: #11998e; border-color: #11998e;
box-shadow: 0 0 0 0.2rem rgba(17, 153, 142, 0.25); box-shadow: 0 0 0 0.2rem rgba(17, 153, 142, 0.25);
} }
.step-wrapper { .step-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
@ -64,8 +63,6 @@
.step-line { height: 2px; width: 36px; margin: 0 6px 18px; } .step-line { height: 2px; width: 36px; margin: 0 6px 18px; }
.step-line.done { background: #11998e; } .step-line.done { background: #11998e; }
.step-line.idle { background: #dee2e6; } .step-line.idle { background: #dee2e6; }
/* OTP Boxes */
.otp-box { .otp-box {
width: 46px !important; width: 46px !important;
height: 54px !important; height: 54px !important;
@ -86,7 +83,13 @@
border-color: #11998e !important; border-color: #11998e !important;
background-color: #f0fff4 !important; background-color: #f0fff4 !important;
} }
/* Kotak OTP saat expired */
.otp-box:disabled {
background-color: #f8f9fa !important;
border-color: #dee2e6 !important;
color: #adb5bd !important;
cursor: not-allowed !important;
}
.email-badge { .email-badge {
background: #f0fff4; background: #f0fff4;
border: 1px solid #11998e33; border: 1px solid #11998e33;
@ -94,7 +97,6 @@
border-radius: 8px; border-radius: 8px;
padding: 10px 14px; padding: 10px 14px;
} }
.icon-box { .icon-box {
width: 64px; height: 64px; width: 64px; height: 64px;
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
@ -160,6 +162,9 @@
</div> </div>
</div> </div>
{{-- =============================================
FORM OTP disabled seluruhnya saat expired
============================================= --}}
<form method="POST" action="{{ route('admin.password.verify-otp') }}" id="otpForm" novalidate> <form method="POST" action="{{ route('admin.password.verify-otp') }}" id="otpForm" novalidate>
@csrf @csrf
@ -170,12 +175,18 @@
{{-- OTP Boxes --}} {{-- OTP Boxes --}}
<div class="d-flex justify-content-center gap-2 mb-2" id="otpBoxes"> <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"> {{-- disabled langsung dari PHP jika backend bilang expired --}}
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off"> @php $isExpiredFromServer = session('otp_expired', false); @endphp
<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"> @for ($i = 0; $i < 6; $i++)
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off"> <input type="text"
<input type="text" class="otp-box form-control" maxlength="1" inputmode="numeric" pattern="[0-9]" autocomplete="off"> class="otp-box form-control"
maxlength="1"
inputmode="numeric"
pattern="[0-9]"
autocomplete="off"
{{ $isExpiredFromServer ? 'disabled' : '' }}>
@endfor
</div> </div>
<input type="hidden" name="otp" id="otpHidden"> <input type="hidden" name="otp" id="otpHidden">
@ -186,32 +197,44 @@
{{-- Countdown Timer --}} {{-- Countdown Timer --}}
<div class="text-center mb-4"> <div class="text-center mb-4">
<div id="timerWrapper"> <div id="timerWrapper" {{ $isExpiredFromServer ? 'style=display:none;' : '' }}>
<p class="text-muted small mb-1">Kode berlaku selama:</p> <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> <span id="countdown" class="badge fs-6 px-3 py-2" style="background:#11998e;">03:00</span>
</div> </div>
<div id="expiredWrapper" style="display:none;"> <div id="expiredWrapper" {{ $isExpiredFromServer ? '' : 'style=display:none;' }}>
<span class="badge bg-danger fs-6 px-3 py-2"> <span class="badge bg-danger fs-6 px-3 py-2">
Kode sudah kadaluarsa Kode sudah kadaluarsa
</span> </span>
</div> </div>
</div> </div>
{{-- Tombol Verifikasi --}}
{{-- disabled dari server jika expired, dari JS jika OTP belum 6 digit --}}
<div class="d-grid mb-3"> <div class="d-grid mb-3">
<button type="submit" class="btn btn-primary-custom py-2 fw-bold" id="submitBtn" disabled> <button type="submit"
class="btn btn-primary-custom py-2 fw-bold"
id="submitBtn"
{{ $isExpiredFromServer ? 'disabled' : 'disabled' }}>
@if($isExpiredFromServer)
Waktu Habis
@else
Verifikasi Kode Verifikasi Kode
@endif
</button> </button>
</div> </div>
</form> </form>
<hr class="my-3"> <hr class="my-3">
{{-- Resend --}} {{-- Resend hanya aktif saat expired --}}
<div class="text-center"> <div class="text-center">
<p class="text-muted small mb-2">Tidak menerima kode?</p> <p class="text-muted small mb-2">Tidak menerima kode?</p>
<form method="POST" action="{{ route('admin.password.resend-otp') }}"> <form method="POST" action="{{ route('admin.password.resend-otp') }}" id="resendForm">
@csrf @csrf
<button type="submit" class="btn btn-outline-secondary btn-sm" id="resendBtn" disabled> <button type="submit"
class="btn btn-outline-secondary btn-sm"
id="resendBtn"
{{ $isExpiredFromServer ? '' : 'disabled' }}>
🔄 Kirim Ulang Kode 🔄 Kirim Ulang Kode
</button> </button>
</form> </form>
@ -231,15 +254,29 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// ── Variabel elemen ──────────────────────────────────────
const boxes = document.querySelectorAll('.otp-box'); const boxes = document.querySelectorAll('.otp-box');
const otpHidden = document.getElementById('otpHidden'); const otpHidden = document.getElementById('otpHidden');
const submitBtn = document.getElementById('submitBtn'); const submitBtn = document.getElementById('submitBtn');
const resendBtn = document.getElementById('resendBtn'); const resendBtn = document.getElementById('resendBtn');
const resendForm = document.getElementById('resendForm');
const countdown = document.getElementById('countdown'); const countdown = document.getElementById('countdown');
const timerWrap = document.getElementById('timerWrapper'); const timerWrap = document.getElementById('timerWrapper');
const expiredWrap= document.getElementById('expiredWrapper'); const expiredWrap = document.getElementById('expiredWrapper');
// ---- OTP input logic ---- // ── Flag dari backend (PHP → JS) ─────────────────────────
// true = backend sudah konfirmasi OTP expired (record dihapus)
// false = backend belum, timer frontend yang jalan
const isExpiredFromServer = {{ $isExpiredFromServer ? 'true' : 'false' }};
// ── Jika sudah expired dari server → langsung lock ───────
if (isExpiredFromServer) {
lockExpired();
return; // stop, tidak perlu jalankan timer
}
// ── OTP input logic ──────────────────────────────────────
boxes.forEach((box, i) => { boxes.forEach((box, i) => {
box.addEventListener('input', function () { box.addEventListener('input', function () {
this.value = this.value.replace(/[^0-9]/g, ''); this.value = this.value.replace(/[^0-9]/g, '');
@ -263,9 +300,13 @@
box.addEventListener('paste', function (e) { box.addEventListener('paste', function (e) {
e.preventDefault(); e.preventDefault();
const pasted = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g, ''); const pasted = (e.clipboardData || window.clipboardData)
.getData('text').replace(/\D/g, '');
pasted.split('').slice(0, 6).forEach((ch, idx) => { pasted.split('').slice(0, 6).forEach((ch, idx) => {
if (boxes[idx]) { boxes[idx].value = ch; boxes[idx].classList.add('filled'); } if (boxes[idx]) {
boxes[idx].value = ch;
boxes[idx].classList.add('filled');
}
}); });
updateHidden(); updateHidden();
boxes[Math.min(pasted.length, 5)].focus(); boxes[Math.min(pasted.length, 5)].focus();
@ -275,29 +316,91 @@
function updateHidden() { function updateHidden() {
const otp = Array.from(boxes).map(b => b.value).join(''); const otp = Array.from(boxes).map(b => b.value).join('');
otpHidden.value = otp; otpHidden.value = otp;
submitBtn.disabled = otp.length !== 6; // Tombol verify aktif hanya jika 6 digit DAN belum expired
submitBtn.disabled = (otp.length !== 6);
} }
// ---- Countdown 10 min ---- // ── Countdown — pakai sessionStorage agar refresh tidak reset ──
let secs = 10 * 60; // Key unik per email agar tidak bentrok jika beda sesi
const timer = setInterval(() => { const STORAGE_KEY = 'otp_timer_{{ md5(session("admin_reset_email", "x")) }}';
secs--; const PENALTY_KEY = 'otp_penalty_{{ md5(session("admin_reset_email", "x")) }}';
const m = String(Math.floor(secs / 60)).padStart(2, '0'); const DURATION = 3 * 60; // 180 detik (3 menit)
const s = String(secs % 60).padStart(2, '0'); const WRONG_PENALTY = 30; // pengurangan waktu saat salah OTP (detik)
countdown.textContent = `${m}:${s}`;
if (secs <= 60) { countdown.style.background = '#ffc107'; countdown.style.color = '#000'; } let startTime = parseInt(sessionStorage.getItem(STORAGE_KEY) || '0');
if (secs <= 0) { if (!startTime || isNaN(startTime)) {
clearInterval(timer); startTime = Date.now();
timerWrap.style.display = 'none'; sessionStorage.setItem(STORAGE_KEY, startTime);
expiredWrap.style.display = 'block'; }
// ── Terapkan penalty jika backend bilang OTP salah ────────
// Backend set session 'otp_wrong' = true saat Hash::check gagal
const wrongFromServer = {{ session('otp_wrong') ? 'true' : 'false' }};
if (wrongFromServer) {
// Tambah penalty ke startTime (maju ke masa lalu = sisa waktu berkurang)
startTime -= (WRONG_PENALTY * 1000);
sessionStorage.setItem(STORAGE_KEY, startTime);
sessionStorage.removeItem(PENALTY_KEY);
}
const timerInterval = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const remaining = DURATION - elapsed;
if (remaining <= 0) {
clearInterval(timerInterval);
sessionStorage.removeItem(STORAGE_KEY);
lockExpired();
return;
}
// Warna kuning saat sisa ≤ 30 detik
if (remaining <= 30) {
countdown.style.background = '#dc3545';
countdown.style.color = '#fff';
} else if (remaining <= 60) {
countdown.style.background = '#ffc107';
countdown.style.color = '#000';
}
const m = String(Math.floor(remaining / 60)).padStart(2, '0');
const s = String(remaining % 60).padStart(2, '0');
countdown.textContent = `${m}:${s}`;
}, 500);
// ── Fungsi lock saat expired ─────────────────────────────
function lockExpired() {
// Sembunyikan timer, tampilkan badge expired
if (timerWrap) timerWrap.style.display = 'none';
if (expiredWrap) expiredWrap.style.display = 'block';
// Disable & ubah teks tombol verifikasi
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.textContent = '⛔ Waktu Habis';
submitBtn.style.background = '#adb5bd';
// Blokir submit form meski tombol diklik paksa (inject JS / inspect)
document.getElementById('otpForm').addEventListener('submit', function (e) {
e.preventDefault();
return false;
});
// Disable semua kotak OTP
boxes.forEach(b => {
b.disabled = true;
b.value = '';
b.classList.remove('filled');
});
// Aktifkan tombol kirim ulang
resendBtn.disabled = false; resendBtn.disabled = false;
} }
}, 1000);
// Enable resend after 60s // ── Bersihkan sessionStorage saat resend dikirim ─────────
setTimeout(() => { resendBtn.disabled = false; }, 60000); resendForm.addEventListener('submit', function () {
sessionStorage.removeItem(STORAGE_KEY);
});
}); });
</script> </script>
</body> </body>

View File

@ -4,26 +4,73 @@
@section('styles') @section('styles')
<style> <style>
/* ── Hero Section ───────────────────────────── */
.hero-image {
max-height: 400px;
object-fit: cover;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
width: 100%;
}
/* Mobile: gambar naik ke atas, teks di bawah */
@media (max-width: 767.98px) {
.hero-section {
text-align: center;
padding-top: 30px !important;
padding-bottom: 30px !important;
}
.hero-img-col {
order: -1;
margin-bottom: 20px;
}
.hero-image {
max-height: 200px;
border-radius: 10px;
}
.hero-section h1 {
font-size: 1.8rem !important;
}
.hero-section p.lead {
font-size: 0.95rem;
}
.hero-section .btn {
width: 100%;
max-width: 300px;
}
}
/* ── Product Card ───────────────────────────── */
.product-card { .product-card {
transition: transform 0.3s ease, box-shadow 0.3s ease; transition: transform 0.3s ease, box-shadow 0.3s ease;
border: none; border: none;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: 0 2px 10px rgba(0,0,0,0.1);
} }
.product-card:hover { .product-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
box-shadow: 0 5px 25px rgba(0,0,0,0.15); box-shadow: 0 5px 25px rgba(0,0,0,0.15);
} }
.product-image { .product-image {
height: 250px; height: 250px;
object-fit: cover; object-fit: cover;
transition: transform 0.3s ease; transition: transform 0.3s ease;
width: 100%;
} }
.product-card:hover .product-image { .product-card:hover .product-image {
transform: scale(1.05); transform: scale(1.05);
} }
@media (max-width: 575.98px) {
.product-image {
height: 150px;
}
.card-body {
padding: 10px;
}
.card-title {
font-size: 0.85rem;
margin-bottom: 4px !important;
}
}
.badge-bestseller { .badge-bestseller {
position: absolute; position: absolute;
@ -44,12 +91,22 @@
font-weight: bold; font-weight: bold;
color: #28a745; color: #28a745;
} }
@media (max-width: 575.98px) {
.hero-image { .price-text {
max-height: 400px; font-size: 0.95rem;
object-fit: cover; margin-bottom: 6px !important;
border-radius: 15px; }
box-shadow: 0 10px 30px rgba(0,0,0,0.2); /* Sembunyikan teks "Terjual" di mobile agar tidak penuh */
.sold-label { display: none; }
/* Quantity input lebih kecil */
.quantity-input { width: 40px !important; font-size: 0.85rem; }
.input-group .btn { padding: 4px 8px; font-size: 0.8rem; }
.btn { font-size: 0.8rem; }
/* Section title */
.display-5 { font-size: 1.4rem !important; }
/* Feature icon lebih kecil */
.feature-icon-circle { width: 56px !important; height: 56px !important; }
.feature-icon-circle .fa-2x { font-size: 1.2rem !important; }
} }
.quantity-input { .quantity-input {
@ -62,17 +119,17 @@
@section('content') @section('content')
<div class="container-fluid p-0"> <div class="container-fluid p-0">
<!-- Hero Section --> <!-- Hero Section -->
<section class="bg-success text-white py-5"> <section class="bg-success text-white py-5 hero-section">
<div class="container"> <div class="container">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-lg-6"> <div class="col-lg-6 col-md-6 col-12">
<h1 class="display-4 fw-bold mb-4">Bibit Cabai Berkualitas</h1> <h1 class="display-4 fw-bold mb-4">Bibit Cabai Berkualitas</h1>
<p class="lead mb-4">Dapatkan bibit cabai terbaik dari Bondowoso untuk hasil panen yang optimal.</p> <p class="lead mb-4">Dapatkan bibit cabai terbaik dari Bondowoso untuk hasil panen yang optimal.</p>
<a href="{{ route('products.best-selling') }}" class="btn btn-light btn-lg"> <a href="{{ route('products.best-selling') }}" class="btn btn-light btn-lg">
<i class="fas fa-fire me-2"></i>Lihat Produk Terlaris <i class="fas fa-fire me-2"></i>Lihat Produk Terlaris
</a> </a>
</div> </div>
<div class="col-lg-6 text-center"> <div class="col-lg-6 col-md-6 col-12 text-center hero-img-col">
<img src="{{ asset('images/bara f1.jpeg') }}" <img src="{{ asset('images/bara f1.jpeg') }}"
alt="Bibit Cabai" alt="Bibit Cabai"
class="img-fluid hero-image" class="img-fluid hero-image"
@ -93,7 +150,7 @@ class="img-fluid hero-image"
@if($featuredProducts->count() > 0) @if($featuredProducts->count() > 0)
<div class="row g-4"> <div class="row g-4">
@foreach($featuredProducts as $product) @foreach($featuredProducts as $product)
<div class="col-lg-4 col-md-6"> <div class="col-lg-4 col-md-6 col-6">
<div class="card product-card h-100 position-relative"> <div class="card product-card h-100 position-relative">
@if($product->total_sold > 0) @if($product->total_sold > 0)
<div class="badge-bestseller"> <div class="badge-bestseller">
@ -114,7 +171,7 @@ class="card-img-top product-image"
<div class="price-text mb-3">Rp {{ number_format($product->price, 0, ',', '.') }}</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 sold-label">
<i class="fas fa-chart-line me-1"></i> <i class="fas fa-chart-line me-1"></i>
Terjual: {{ number_format($product->total_sold) }} bibit Terjual: {{ number_format($product->total_sold) }} bibit
</small> </small>
@ -198,6 +255,7 @@ class="card-img-top product-image"
@endif @endif
</div> </div>
</section> </section>
<!-- Semua Produk Section --> <!-- Semua Produk Section -->
<section class="py-5 bg-white"> <section class="py-5 bg-white">
<div class="container"> <div class="container">
@ -208,7 +266,7 @@ class="card-img-top product-image"
<div class="row g-4"> <div class="row g-4">
@foreach($allProducts as $product) @foreach($allProducts as $product)
<div class="col-lg-4 col-md-6"> <div class="col-lg-4 col-md-6 col-6">
<div class="card product-card h-100 position-relative"> <div class="card product-card h-100 position-relative">
<div class="overflow-hidden"> <div class="overflow-hidden">
<img src="{{ $product->image ? asset('storage/' . $product->image) : 'https://via.placeholder.com/300x250/28a745/ffffff?text=' . urlencode($product->name) }}" <img src="{{ $product->image ? asset('storage/' . $product->image) : 'https://via.placeholder.com/300x250/28a745/ffffff?text=' . urlencode($product->name) }}"
@ -268,41 +326,31 @@ class="card-img-top product-image"
</div> </div>
</section> </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">
<div class="row g-4"> <div class="row g-4">
<div class="col-lg-3 col-md-6"> <div class="col-lg-3 col-md-6 col-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 feature-icon-circle" style="width: 80px; height: 80px;">
<i class="fas fa-seedling fa-2x"></i> <i class="fas fa-seedling fa-2x"></i>
</div> </div>
<h5>Bibit Berkualitas</h5> <h5>Bibit Berkualitas</h5>
<p class="text-muted">Bibit pilihan dengan kualitas terjamin dan hasil optimal.</p> <p class="text-muted">Bibit pilihan dengan kualitas terjamin dan hasil optimal.</p>
</div> </div>
</div> </div>
<div class="col-lg-3 col-md-6"> <div class="col-lg-3 col-md-6 col-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 feature-icon-circle" style="width: 80px; height: 80px;">
<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 kabupaten Bondowoso 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 col-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 feature-icon-circle" style="width: 80px; height: 80px;">
<i class="fas fa-medal fa-2x"></i>
</div>
<h5>Garansi Tumbuh</h5>
<p class="text-muted">Garansi bibit tumbuh atau uang kembali 100%.</p>
</div>
</div> -->
<div class="col-lg-3 col-md-6">
<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;">
<i class="fas fa-headset fa-2x"></i> <i class="fas fa-headset fa-2x"></i>
</div> </div>
<h5>Support 24/7</h5> <h5>Support 24/7</h5>