diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php
index 6eb248f..e79dae3 100644
--- a/app/Http/Controllers/Admin/DashboardController.php
+++ b/app/Http/Controllers/Admin/DashboardController.php
@@ -5,14 +5,40 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
+use App\Models\Classification; // Import model Classification
+use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
{
public function index()
{
$user = Auth::user();
+
+ // Mulai Query Dasar
+ $query = Classification::query();
+
+ // LOGIKA ROLE: Jika yang login adalah 'user', kunci hanya data miliknya
+ if ($user->role === 'user') {
+ $query->where('user_id', $user->id);
+ }
+ // Jika admin, biarkan query mengambil semua data (tanpa where)
+
+ // Ambil data Chart (sudah terfilter role)
+ $chartData = (clone $query)->select('result', DB::raw('count(*) as total'))
+ ->groupBy('result')
+ ->get();
+
+ // Ambil History Terbaru (sudah terfilter role)
+ $recentHistory = (clone $query)->latest()->limit(10)->get();
+
+ // Hitung Total Scan (sudah terfilter role)
+ $totalScan = (clone $query)->count();
+
return inertia('admin/Dashboard', [
'user' => $user,
+ 'chartData' => $chartData,
+ 'recentHistory' => $recentHistory,
+ 'totalScan' => $totalScan,
]);
}
-}
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Public/ClassificationController.php b/app/Http/Controllers/Public/ClassificationController.php
new file mode 100644
index 0000000..bae2f75
--- /dev/null
+++ b/app/Http/Controllers/Public/ClassificationController.php
@@ -0,0 +1,32 @@
+validate([
+ 'image' => 'required|image|max:2048',
+ 'result' => 'required|string',
+ 'confidence' => 'required',
+ ]);
+
+ // Simpan gambar ke storage public
+ $path = $request->file('image')->store('classifications', 'public');
+
+ // Simpan ke Database
+ Classification::create([
+ 'user_id' => auth()->id(), // null jika guest, id jika login
+ 'image_path' => $path,
+ 'result' => $request->result,
+ 'confidence' => (float) filter_var($request->confidence, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
+ ]);
+
+ return response()->json(['message' => 'Data tersimpan ke history']);
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php
index 587b929..fc594d5 100644
--- a/app/Http/Middleware/HandleInertiaRequests.php
+++ b/app/Http/Middleware/HandleInertiaRequests.php
@@ -39,12 +39,18 @@ public function share(Request $request): array
...parent::share($request),
'name' => config('app.name'),
'auth' => [
- 'user' => $request->user(),
+ 'user' => $request->user() ? [
+ 'id' => $request->user()->id,
+ 'name' => $request->user()->name,
+ 'email' => $request->user()->email,
+ 'role' => $request->user()->role, // Memastikan role terkirim dengan jelas
+ // Jangan kirim password_hash atau data sensitif lainnya di sini
+ ] : null,
],
- 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
+ 'sidebarOpen' => !$request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
'flash' => [
- 'success' => fn () => $request->session()->get('success'),
- 'error' => fn () => $request->session()->get('error'),
+ 'success' => fn() => $request->session()->get('success'),
+ 'error' => fn() => $request->session()->get('error'),
],
];
}
diff --git a/app/Http/Middleware/RoleMiddleware.php b/app/Http/Middleware/RoleMiddleware.php
new file mode 100644
index 0000000..471735f
--- /dev/null
+++ b/app/Http/Middleware/RoleMiddleware.php
@@ -0,0 +1,20 @@
+belongsTo(User::class);
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index f40a71d..8cebb7a 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -11,7 +11,7 @@
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
-#[Fillable(['name', 'email', 'password'])]
+#[Fillable(['name', 'email', 'password', 'role'])]
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
class User extends Authenticatable
{
diff --git a/bootstrap/app.php b/bootstrap/app.php
index c4f1cc5..99fe930 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -2,6 +2,7 @@
use App\Http\Middleware\HandleAppearance;
use App\Http\Middleware\HandleInertiaRequests;
+use App\Http\Middleware\RoleMiddleware;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
@@ -9,13 +10,17 @@
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
- web: __DIR__.'/../routes/web.php',
- commands: __DIR__.'/../routes/console.php',
+ web: __DIR__ . '/../routes/web.php',
+ commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->encryptCookies(except: ['appearance', 'sidebar_state']);
+ $middleware->alias([
+ 'role' => RoleMiddleware::class,
+ ]);
+
$middleware->web(append: [
HandleAppearance::class,
HandleInertiaRequests::class,
diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php
index 05fb5d9..3d07fe3 100644
--- a/database/migrations/0001_01_01_000000_create_users_table.php
+++ b/database/migrations/0001_01_01_000000_create_users_table.php
@@ -17,6 +17,9 @@ public function up(): void
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
+
+ $table->string('role')->default('user');
+
$table->rememberToken();
$table->timestamps();
});
diff --git a/database/migrations/2026_04_25_143239_create_classifications_table.php b/database/migrations/2026_04_25_143239_create_classifications_table.php
new file mode 100644
index 0000000..ad5d214
--- /dev/null
+++ b/database/migrations/2026_04_25_143239_create_classifications_table.php
@@ -0,0 +1,32 @@
+id();
+ // user_id boleh kosong (untuk guest)
+ $table->foreignId('user_id')->nullable()->constrained()->onDelete('cascade');
+ $table->string('image_path');
+ $table->string('result');
+ $table->float('confidence');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('classifications');
+ }
+};
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index d01a0ef..99353ec 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -5,6 +5,7 @@
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
+use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
@@ -16,8 +17,10 @@ public function run(): void
// User::factory(10)->create();
User::factory()->create([
- 'name' => 'Test User',
- 'email' => 'test@example.com',
+ 'name' => 'Admin',
+ 'email' => 'admin@example.com',
+ 'password' => Hash::make('password'),
+ 'role' => 'super_user',
]);
}
}
diff --git a/package-lock.json b/package-lock.json
index c8761f7..b13fddd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"@inertiajs/vue3": "^3.0.0",
"@tanstack/vue-table": "^8.21.3",
"@vueuse/core": "^12.8.2",
+ "chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"laravel-vite-plugin": "^3.0.0",
@@ -18,6 +19,7 @@
"tailwindcss": "^4.1.1",
"tw-animate-css": "^1.2.5",
"vue": "^3.5.13",
+ "vue-chartjs": "^5.3.3",
"vue-input-otp": "^0.3.2",
"vue-sonner": "^2.0.0",
"ziggy-js": "^2.6.2"
@@ -575,6 +577,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@@ -2505,6 +2513,19 @@
"node": ">=8"
}
},
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -6625,6 +6646,16 @@
}
}
},
+ "node_modules/vue-chartjs": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz",
+ "integrity": "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "vue": "^3.0.0-0 || ^2.7.0"
+ }
+ },
"node_modules/vue-eslint-parser": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.0.tgz",
diff --git a/package.json b/package.json
index d62ad65..1ad2ddb 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"@inertiajs/vue3": "^3.0.0",
"@tanstack/vue-table": "^8.21.3",
"@vueuse/core": "^12.8.2",
+ "chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"laravel-vite-plugin": "^3.0.0",
@@ -46,6 +47,7 @@
"tailwindcss": "^4.1.1",
"tw-animate-css": "^1.2.5",
"vue": "^3.5.13",
+ "vue-chartjs": "^5.3.3",
"vue-input-otp": "^0.3.2",
"vue-sonner": "^2.0.0",
"ziggy-js": "^2.6.2"
diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue
index 8f1548e..a7ab867 100644
--- a/resources/js/components/AppSidebar.vue
+++ b/resources/js/components/AppSidebar.vue
@@ -1,11 +1,13 @@
@@ -13,4 +19,4 @@ const { breadcrumbs = [] } = defineProps<{
- ini adalah dashboard admin -
-Akumulasi seluruh pemindaian
+| Tanggal | +Hasil | +Akurasi | +
|---|---|---|
| + {{ new Date(item.created_at).toLocaleDateString('id-ID') }} + | +{{ item.result }} | +{{ item.confidence }}% | +
| Tidak ada data. | +||