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 @@ + \ No newline at end of file diff --git a/resources/js/pages/admin/Dashboard.vue b/resources/js/pages/admin/Dashboard.vue index 5fc606f..51d8109 100644 --- a/resources/js/pages/admin/Dashboard.vue +++ b/resources/js/pages/admin/Dashboard.vue @@ -1,10 +1,15 @@ + \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 419911f..95a39d1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,6 +4,7 @@ use Laravel\Fortify\Features; use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\Admin\DashboardController; +use App\Http\Controllers\Public\ClassificationController; // Import Controller Route::redirect('dashboard', 'admin/dashboard')->name('dashboard'); @@ -13,9 +14,14 @@ ]); })->name('home'); +Route::post('/classifications', [ClassificationController::class, 'store'])->name('public.classifications.store'); + Route::prefix('admin')->name('admin.')->middleware(['auth', 'verified'])->group(function () { Route::get('dashboard', [DashboardController::class, 'index'])->name('dashboard'); Route::resource('users', UserController::class)->names('users'); + + // Route tambahan untuk Admin jika ingin melihat history klasifikasi secara global + // Route::get('history', [DashboardController::class, 'history'])->name('history'); });