+ */
+ public function definition(): array
+ {
+ return [
+ 'name' => fake()->name(),
+ 'email' => fake()->unique()->safeEmail(),
+ 'email_verified_at' => now(),
+ 'password' => static::$password ??= Hash::make('password'),
+ 'remember_token' => Str::random(10),
+ ];
+ }
+
+ /**
+ * Indicate that the model's email address should be unverified.
+ */
+ public function unverified(): static
+ {
+ return $this->state(fn (array $attributes) => [
+ 'email_verified_at' => null,
+ ]);
+ }
+}
diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php
new file mode 100644
index 0000000..b9c106b
--- /dev/null
+++ b/database/migrations/0001_01_01_000001_create_cache_table.php
@@ -0,0 +1,35 @@
+string('key')->primary();
+ $table->mediumText('value');
+ $table->integer('expiration');
+ });
+
+ Schema::create('cache_locks', function (Blueprint $table) {
+ $table->string('key')->primary();
+ $table->string('owner');
+ $table->integer('expiration');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('cache');
+ Schema::dropIfExists('cache_locks');
+ }
+};
diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php
new file mode 100644
index 0000000..425e705
--- /dev/null
+++ b/database/migrations/0001_01_01_000002_create_jobs_table.php
@@ -0,0 +1,57 @@
+id();
+ $table->string('queue')->index();
+ $table->longText('payload');
+ $table->unsignedTinyInteger('attempts');
+ $table->unsignedInteger('reserved_at')->nullable();
+ $table->unsignedInteger('available_at');
+ $table->unsignedInteger('created_at');
+ });
+
+ Schema::create('job_batches', function (Blueprint $table) {
+ $table->string('id')->primary();
+ $table->string('name');
+ $table->integer('total_jobs');
+ $table->integer('pending_jobs');
+ $table->integer('failed_jobs');
+ $table->longText('failed_job_ids');
+ $table->mediumText('options')->nullable();
+ $table->integer('cancelled_at')->nullable();
+ $table->integer('created_at');
+ $table->integer('finished_at')->nullable();
+ });
+
+ Schema::create('failed_jobs', function (Blueprint $table) {
+ $table->id();
+ $table->string('uuid')->unique();
+ $table->text('connection');
+ $table->text('queue');
+ $table->longText('payload');
+ $table->longText('exception');
+ $table->timestamp('failed_at')->useCurrent();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('jobs');
+ Schema::dropIfExists('job_batches');
+ Schema::dropIfExists('failed_jobs');
+ }
+};
diff --git a/database/migrations/2025_10_16_071401_create_users_table.php b/database/migrations/2025_10_16_071401_create_users_table.php
new file mode 100644
index 0000000..ad0c86a
--- /dev/null
+++ b/database/migrations/2025_10_16_071401_create_users_table.php
@@ -0,0 +1,31 @@
+id();
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+});
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('users');
+ }
+};
diff --git a/database/migrations/2026_02_12_073657_create_diagnoses_table.php b/database/migrations/2026_02_12_073657_create_diagnoses_table.php
new file mode 100644
index 0000000..b80460d
--- /dev/null
+++ b/database/migrations/2026_02_12_073657_create_diagnoses_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->unsignedBigInteger('user_id');
+ $table->string('plant_name')->nullable();
+ $table->text('symptoms')->nullable();
+ $table->text('diagnosis')->nullable();
+ $table->timestamps();
+
+ $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
+ });
+}
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('diagnoses');
+ }
+};
diff --git a/database/migrations/2026_02_18_060420_add_cf_columns_to_diagnoses_table.php b/database/migrations/2026_02_18_060420_add_cf_columns_to_diagnoses_table.php
new file mode 100644
index 0000000..4bf39de
--- /dev/null
+++ b/database/migrations/2026_02_18_060420_add_cf_columns_to_diagnoses_table.php
@@ -0,0 +1,28 @@
+string('disease_name')->nullable()->after('symptoms');
+ $table->text('treatment')->nullable()->after('disease_name');
+ $table->float('confidence')->default(0)->after('treatment');
+ $table->json('cf_results')->nullable()->after('confidence');
+ });
+}
+
+public function down(): void
+{
+ Schema::table('diagnoses', function (Blueprint $table) {
+ $table->dropColumn(['disease_name', 'treatment', 'confidence', 'cf_results']);
+ });
+}
+};
diff --git a/database/migrations/2026_02_18_061719_create_sessions_table.php b/database/migrations/2026_02_18_061719_create_sessions_table.php
new file mode 100644
index 0000000..f60625b
--- /dev/null
+++ b/database/migrations/2026_02_18_061719_create_sessions_table.php
@@ -0,0 +1,31 @@
+string('id')->primary();
+ $table->foreignId('user_id')->nullable()->index();
+ $table->string('ip_address', 45)->nullable();
+ $table->text('user_agent')->nullable();
+ $table->longText('payload');
+ $table->integer('last_activity')->index();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('sessions');
+ }
+};
diff --git a/database/migrations/2026_03_28_073441_create_password_reset_otps_table.php b/database/migrations/2026_03_28_073441_create_password_reset_otps_table.php
new file mode 100644
index 0000000..ec7c5ad
--- /dev/null
+++ b/database/migrations/2026_03_28_073441_create_password_reset_otps_table.php
@@ -0,0 +1,25 @@
+id();
+ $table->string('email')->index();
+ $table->string('otp', 6);
+ $table->timestamp('expires_at');
+ $table->boolean('is_used')->default(false);
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('password_reset_otps');
+ }
+};
\ No newline at end of file
diff --git a/database/migrations/2026_03_29_070616_add_photo_to_users_table.php b/database/migrations/2026_03_29_070616_add_photo_to_users_table.php
new file mode 100644
index 0000000..d953ce1
--- /dev/null
+++ b/database/migrations/2026_03_29_070616_add_photo_to_users_table.php
@@ -0,0 +1,25 @@
+string('photo')->nullable()->after('email');
+ });
+}
+
+public function down(): void
+{
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn('photo');
+ });
+}
+};
diff --git a/database/migrations/2026_03_29_072038_create_notifications_table.php b/database/migrations/2026_03_29_072038_create_notifications_table.php
new file mode 100644
index 0000000..7a97c2f
--- /dev/null
+++ b/database/migrations/2026_03_29_072038_create_notifications_table.php
@@ -0,0 +1,26 @@
+id();
+ $table->foreignId('user_id')->constrained()->onDelete('cascade');
+ $table->string('type'); // diagnosis, reminder, system
+ $table->string('title');
+ $table->text('message');
+ $table->boolean('is_read')->default(false);
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('notifications');
+ }
+};
\ No newline at end of file
diff --git a/database/migrations/2026_03_29_120320_create_diseases_table.php b/database/migrations/2026_03_29_120320_create_diseases_table.php
new file mode 100644
index 0000000..c3c34ab
--- /dev/null
+++ b/database/migrations/2026_03_29_120320_create_diseases_table.php
@@ -0,0 +1,32 @@
+id();
+ $table->string('code')->unique(); // P01, P02, dst
+ $table->string('name');
+ $table->string('latin_name')->nullable();
+ $table->text('description')->nullable();
+ $table->string('photo')->nullable();
+ $table->timestamps();
+ });
+}
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('diseases');
+ }
+};
diff --git a/database/migrations/2026_03_29_120341_create_symptoms_table.php b/database/migrations/2026_03_29_120341_create_symptoms_table.php
new file mode 100644
index 0000000..eeec49c
--- /dev/null
+++ b/database/migrations/2026_03_29_120341_create_symptoms_table.php
@@ -0,0 +1,30 @@
+id();
+ $table->string('code')->unique(); // G01, G02, dst
+ $table->string('name');
+ $table->string('photo')->nullable();
+ $table->timestamps();
+ });
+}
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('symptoms');
+ }
+};
diff --git a/database/migrations/2026_03_29_120347_create_treatments_table.php b/database/migrations/2026_03_29_120347_create_treatments_table.php
new file mode 100644
index 0000000..0c04ff0
--- /dev/null
+++ b/database/migrations/2026_03_29_120347_create_treatments_table.php
@@ -0,0 +1,31 @@
+id();
+ $table->string('code')->unique(); // S01, S02, dst
+ $table->foreignId('disease_id')->constrained()->onDelete('cascade');
+ $table->text('description');
+ $table->integer('order')->default(1);
+ $table->timestamps();
+ });
+}
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('treatments');
+ }
+};
diff --git a/database/migrations/2026_03_29_120501_create_disease_symptoms_table.php b/database/migrations/2026_03_29_120501_create_disease_symptoms_table.php
new file mode 100644
index 0000000..8d85f76
--- /dev/null
+++ b/database/migrations/2026_03_29_120501_create_disease_symptoms_table.php
@@ -0,0 +1,29 @@
+id();
+ $table->foreignId('disease_id')->constrained()->onDelete('cascade');
+ $table->foreignId('symptom_id')->constrained()->onDelete('cascade');
+ $table->float('cf_value')->default(0.5);
+ });
+}
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('disease_symptoms');
+ }
+};
diff --git a/database/migrations/2026_03_30_034752_add_role_to_users_table.php b/database/migrations/2026_03_30_034752_add_role_to_users_table.php
new file mode 100644
index 0000000..f941c7b
--- /dev/null
+++ b/database/migrations/2026_03_30_034752_add_role_to_users_table.php
@@ -0,0 +1,23 @@
+enum('role', ['admin', 'user'])->default('user')->after('email');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn('role');
+ });
+ }
+};
\ No newline at end of file
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
new file mode 100644
index 0000000..cf970bd
--- /dev/null
+++ b/database/seeders/DatabaseSeeder.php
@@ -0,0 +1,15 @@
+call([
+ DiseaseSeeder::class,
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/database/seeders/DiseaseSeeder.php b/database/seeders/DiseaseSeeder.php
new file mode 100644
index 0000000..97f0ec8
--- /dev/null
+++ b/database/seeders/DiseaseSeeder.php
@@ -0,0 +1,140 @@
+ 'P01', 'name' => 'Luka Api (Leaf Scald)',
+ 'latin_name' => 'Xanthomonas albilineans',
+ 'description' => 'Penyakit bakteri yang menyebabkan garis putih memanjang pada daun tebu',
+ 'symptoms' => [
+ ['code'=>'G01','name'=>'Garis putih memanjang pada daun (stripe putih)','cf'=>0.90],
+ ['code'=>'G02','name'=>'Daun layu dan mengering','cf'=>0.70],
+ ['code'=>'G03','name'=>'Tanaman kerdil / tumbuh tidak normal','cf'=>0.60],
+ ['code'=>'G15','name'=>'Daun terlihat kusam / tidak segar','cf'=>0.50],
+ ['code'=>'G18','name'=>'Warna daun pucat kekuningan','cf'=>0.60],
+ ['code'=>'G20','name'=>'Mata tunas mati','cf'=>0.70],
+ ],
+ 'treatments' => [
+ 'Gunakan bibit sehat dan bebas penyakit',
+ 'Sterilisasi alat potong dengan larutan formalin 2%',
+ 'Tanam varietas tahan seperti PS 881, BL',
+ 'Buang dan bakar tanaman yang terinfeksi',
+ 'Rotasi tanaman dengan palawija',
+ ],
+ ],
+ [
+ 'code' => 'P02', 'name' => 'Pokahbung',
+ 'latin_name' => 'Fusarium moniliforme',
+ 'description' => 'Penyakit jamur yang menyebabkan busuk pada batang tebu dan munculnya tunas abnormal',
+ 'symptoms' => [
+ ['code'=>'G04','name'=>'Batang busuk dari dalam','cf'=>0.90],
+ ['code'=>'G05','name'=>'Bau busuk pada batang','cf'=>0.80],
+ ['code'=>'G06','name'=>'Munculnya tunas-tunas kecil abnormal (pokah)','cf'=>0.90],
+ ['code'=>'G16','name'=>'Tanaman mudah rebah','cf'=>0.70],
+ ['code'=>'G19','name'=>'Luka memanjang pada batang','cf'=>0.60],
+ ['code'=>'G20','name'=>'Mata tunas mati','cf'=>0.70],
+ ],
+ 'treatments' => [
+ 'Gunakan bibit dari bagian pangkal batang',
+ 'Rendam bibit dengan fungisida (Benlate 2g/liter selama 30 menit)',
+ 'Hindari luka mekanis pada batang saat penanaman',
+ 'Perbaiki sistem drainase lahan agar tidak tergenang',
+ 'Tanam varietas tahan seperti PS 862, PS 881',
+ ],
+ ],
+ [
+ 'code' => 'P03', 'name' => 'Karat Daun (Leaf Rust)',
+ 'latin_name' => 'Puccinia melanocephala',
+ 'description' => 'Penyakit jamur yang menyebabkan bercak karat kemerahan pada permukaan daun',
+ 'symptoms' => [
+ ['code'=>'G07','name'=>'Bercak coklat kemerahan pada daun','cf'=>0.90],
+ ['code'=>'G08','name'=>'Bercak berkembang menjadi pustula karat','cf'=>0.95],
+ ['code'=>'G09','name'=>'Daun mengering dimulai dari ujung','cf'=>0.70],
+ ['code'=>'G15','name'=>'Daun terlihat kusam / tidak segar','cf'=>0.60],
+ ['code'=>'G17','name'=>'Produksi turun drastis','cf'=>0.60],
+ ],
+ 'treatments' => [
+ 'Semprot dengan fungisida berbahan aktif Mancozeb',
+ 'Tanam varietas tahan seperti PS 862, PSJT 941',
+ 'Jaga jarak tanam yang optimal (120–130 cm)',
+ 'Bersihkan gulma secara rutin di sekitar tanaman',
+ 'Pangkas dan musnahkan daun yang terinfeksi berat',
+ ],
+ ],
+ [
+ 'code' => 'P04', 'name' => 'Mozaik',
+ 'latin_name' => 'Sugarcane Mosaic Virus (SCMV)',
+ 'description' => 'Penyakit virus yang menyebabkan pola belang hijau tua dan hijau muda pada daun',
+ 'symptoms' => [
+ ['code'=>'G10','name'=>'Belang hijau tua dan hijau muda pada daun (mozaik)','cf'=>0.95],
+ ['code'=>'G11','name'=>'Pertumbuhan tanaman terhambat','cf'=>0.70],
+ ['code'=>'G15','name'=>'Daun terlihat kusam / tidak segar','cf'=>0.60],
+ ['code'=>'G17','name'=>'Produksi turun drastis','cf'=>0.70],
+ ['code'=>'G18','name'=>'Warna daun pucat kekuningan','cf'=>0.50],
+ ],
+ 'treatments' => [
+ 'Gunakan bibit sehat yang bebas virus',
+ 'Kendalikan vektor kutu daun dengan insektisida sistemik',
+ 'Cabut dan musnahkan tanaman yang terinfeksi virus',
+ 'Tanam varietas tahan terhadap SCMV',
+ 'Rotasi dengan tanaman non-inang virus selama 1 musim',
+ ],
+ ],
+ [
+ 'code' => 'P05', 'name' => 'Ratoon Stunting Disease (RSD)',
+ 'latin_name' => 'Leifsonia xyli subsp. xyli',
+ 'description' => 'Penyakit bakteri sistemik yang menghambat pertumbuhan dan menurunkan produksi gula',
+ 'symptoms' => [
+ ['code'=>'G03','name'=>'Tanaman kerdil / tumbuh tidak normal','cf'=>0.80],
+ ['code'=>'G11','name'=>'Pertumbuhan tanaman terhambat','cf'=>0.90],
+ ['code'=>'G12','name'=>'Ruas batang memendek','cf'=>0.85],
+ ['code'=>'G13','name'=>'Pembentukan gula terhambat','cf'=>0.80],
+ ['code'=>'G14','name'=>'Pembuluh batang berwarna merah kecoklatan','cf'=>0.90],
+ ['code'=>'G17','name'=>'Produksi turun drastis','cf'=>0.80],
+ ],
+ 'treatments' => [
+ 'Perlakuan air panas pada bibit (suhu 50°C selama 2 jam)',
+ 'Sterilisasi alat potong sebelum dan sesudah digunakan',
+ 'Gunakan bibit dari hasil kultur jaringan yang bersertifikat',
+ 'Tanam varietas toleran RSD yang direkomendasikan',
+ 'Hindari penggunaan bibit dari kebun yang terindikasi terinfeksi',
+ ],
+ ],
+ ];
+
+ $treatmentCounter = 1;
+ foreach ($data as $d) {
+ $disease = Disease::create([
+ 'code' => $d['code'],
+ 'name' => $d['name'],
+ 'latin_name' => $d['latin_name'],
+ 'description' => $d['description'],
+ ]);
+
+ foreach ($d['symptoms'] as $s) {
+ $symptom = Symptom::firstOrCreate(
+ ['code' => $s['code']],
+ ['name' => $s['name']]
+ );
+ $disease->symptoms()->attach($symptom->id, ['cf_value' => $s['cf']]);
+ }
+
+ foreach ($d['treatments'] as $t) {
+ Treatment::create([
+ 'code' => 'S' . str_pad($treatmentCounter++, 2, '0', STR_PAD_LEFT),
+ 'disease_id' => $disease->id,
+ 'description' => $t,
+ 'order' => array_search($t, $d['treatments']) + 1,
+ ]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..86aa6b9
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,19 @@
+
+ AddType application/x-httpd-lsphp .php
+
+
+
+
+ Options -MultiViews -Indexes
+
+ RewriteEngine On
+ RewriteBase /E31232094/public/
+ RewriteCond %{HTTP:Authorization} .
+ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_URI} (.+)/$
+ RewriteRule ^ %1 [L,R=301]
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..e69de29
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..8a4900b
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,17 @@
+handleRequest(Request::capture());
\ No newline at end of file
diff --git a/public/js/tour.js b/public/js/tour.js
new file mode 100644
index 0000000..42b9d1c
--- /dev/null
+++ b/public/js/tour.js
@@ -0,0 +1,181 @@
+// tour.js — SiPakarTebu Onboarding Tour
+const tourSteps = [
+ {
+ title: "Selamat datang di SiPakarTebu! 🌱",
+ desc: "Ini adalah panduan singkat untuk membantu kamu memahami fitur-fitur utama aplikasi.",
+ target: null,
+ },
+ {
+ title: "Menu Navigasi",
+ desc: "Gunakan menu di sebelah kiri untuk berpindah antar halaman: Dashboard, Diagnosa, Riwayat, Kamus, dan Profil.",
+ target: "#sidebar",
+ pos: "right",
+ },
+ {
+ title: "Statistik Kamu",
+ desc: "Ringkasan total diagnosa yang sudah kamu lakukan dan akurasi rata-rata model.",
+ target: ".stat-cards",
+ pos: "bottom",
+ },
+ {
+ title: "Grafik Diagnosa",
+ desc: "Grafik ini menunjukkan aktivitas diagnosamu per bulan sepanjang tahun.",
+ target: ".chart-container",
+ pos: "top",
+ },
+ {
+ title: "Mulai Diagnosa Baru",
+ desc: "Klik menu Diagnosa untuk memulai diagnosa penyakit tebu. Unggah foto dan dapatkan hasilnya!",
+ target: "#nav-diagnosa",
+ pos: "right",
+ },
+];
+
+let tourCurrent = 0;
+
+function createTourOverlay() {
+ const overlay = document.createElement("div");
+ overlay.id = "tour-overlay";
+ overlay.style.cssText = `
+ position: fixed; inset: 0; z-index: 9999;
+ pointer-events: none;
+ `;
+ document.body.appendChild(overlay);
+ return overlay;
+}
+
+function createTooltip() {
+ const el = document.createElement("div");
+ el.id = "tour-tooltip";
+ el.style.cssText = `
+ position: fixed; z-index: 10000;
+ background: #fff; border-radius: 14px;
+ padding: 20px 22px; width: 280px;
+ box-shadow: 0 8px 40px rgba(0,0,0,0.22);
+ pointer-events: auto;
+ font-family: inherit;
+ `;
+ document.body.appendChild(el);
+ return el;
+}
+
+function createHighlight() {
+ const el = document.createElement("div");
+ el.id = "tour-highlight";
+ el.style.cssText = `
+ position: fixed; z-index: 9998;
+ border: 2.5px solid #2e7d52;
+ border-radius: 10px;
+ pointer-events: none;
+ transition: all 0.35s ease;
+ box-shadow: 0 0 0 9999px rgba(0,0,0,0.50);
+ `;
+ document.body.appendChild(el);
+ return el;
+}
+
+function renderTourStep() {
+ const step = tourSteps[tourCurrent];
+ const total = tourSteps.length;
+
+ const highlight = document.getElementById("tour-highlight");
+ const tooltip = document.getElementById("tour-tooltip");
+
+ // Highlight target element
+ if (step.target) {
+ const target = document.querySelector(step.target);
+ if (target) {
+ const r = target.getBoundingClientRect();
+ const pad = 8;
+ highlight.style.display = "block";
+ highlight.style.top = (r.top - pad) + "px";
+ highlight.style.left = (r.left - pad) + "px";
+ highlight.style.width = (r.width + pad * 2) + "px";
+ highlight.style.height = (r.height + pad * 2) + "px";
+
+ // Position tooltip near target
+ if (step.pos === "right") {
+ tooltip.style.top = r.top + "px";
+ tooltip.style.left = (r.right + 16) + "px";
+ } else if (step.pos === "bottom") {
+ tooltip.style.top = (r.bottom + 16) + "px";
+ tooltip.style.left = Math.max(16, r.left) + "px";
+ } else if (step.pos === "top") {
+ tooltip.style.top = (r.top - 200) + "px";
+ tooltip.style.left = Math.max(16, r.left) + "px";
+ } else {
+ tooltip.style.top = "50%";
+ tooltip.style.left = "50%";
+ tooltip.style.transform = "translate(-50%, -50%)";
+ }
+ }
+ } else {
+ highlight.style.display = "none";
+ tooltip.style.top = "50%";
+ tooltip.style.left = "50%";
+ tooltip.style.transform = "translate(-50%, -50%)";
+ }
+
+ const dots = Array.from({ length: total }, (_, i) =>
+ `
`
+ ).join("");
+
+ tooltip.innerHTML = `
+
+ Langkah ${tourCurrent + 1} dari ${total}
+
+
+ ${step.title}
+
+
+ ${step.desc}
+
+
+
${dots}
+
+ Lewati
+
+ ${tourCurrent === total - 1 ? "Selesai ✓" : "Lanjut →"}
+
+
+
+ `;
+}
+
+function tourNext() {
+ tourCurrent++;
+ if (tourCurrent >= tourSteps.length) {
+ endTour();
+ return;
+ }
+ renderTourStep();
+}
+
+function endTour() {
+ ["tour-overlay", "tour-tooltip", "tour-highlight"].forEach((id) => {
+ const el = document.getElementById(id);
+ if (el) el.remove();
+ });
+ // Tandai sudah pernah tour
+ localStorage.setItem("sipakartebu_tour_done", "1");
+}
+
+function startTour() {
+ tourCurrent = 0;
+ if (!document.getElementById("tour-overlay")) createTourOverlay();
+ if (!document.getElementById("tour-tooltip")) createTooltip();
+ if (!document.getElementById("tour-highlight")) createHighlight();
+ renderTourStep();
+}
+
+// Auto-start untuk pengguna baru
+document.addEventListener("DOMContentLoaded", () => {
+ if (!localStorage.getItem("sipakartebu_tour_done")) {
+ setTimeout(startTour, 800); // delay sedikit biar halaman load dulu
+ }
+});
\ No newline at end of file
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..eb05362
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/resources/css/app.css b/resources/css/app.css
new file mode 100644
index 0000000..3e6abea
--- /dev/null
+++ b/resources/css/app.css
@@ -0,0 +1,11 @@
+@import 'tailwindcss';
+
+@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
+@source '../../storage/framework/views/*.php';
+@source '../**/*.blade.php';
+@source '../**/*.js';
+
+@theme {
+ --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+ 'Segoe UI Symbol', 'Noto Color Emoji';
+}
diff --git a/resources/js/app.js b/resources/js/app.js
new file mode 100644
index 0000000..e59d6a0
--- /dev/null
+++ b/resources/js/app.js
@@ -0,0 +1 @@
+import './bootstrap';
diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js
new file mode 100644
index 0000000..5f1390b
--- /dev/null
+++ b/resources/js/bootstrap.js
@@ -0,0 +1,4 @@
+import axios from 'axios';
+window.axios = axios;
+
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php
new file mode 100644
index 0000000..357bb5c
--- /dev/null
+++ b/resources/views/auth/forgot-password.blade.php
@@ -0,0 +1,129 @@
+
+
+
+
+
+ Lupa Password - SiPakarTebu
+
+
+
+
+
+
+
Lupa Password?
+
Masukkan email terdaftar untuk menerima kode OTP
+
+ @if(session('status'))
+
{{ session('status') }}
+ @endif
+
+ @if($errors->any())
+
{{ $errors->first() }}
+ @endif
+
+
+
+
← Kembali ke Login
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php
new file mode 100644
index 0000000..67d21f3
--- /dev/null
+++ b/resources/views/auth/login.blade.php
@@ -0,0 +1,386 @@
+
+
+
+
+
+ Login - SiPakarTebu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PAKAR TEBU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SiPakarTebu
+
+
+
+
+
+ 🌿 SISTEM PAKAR TEBU
+
+
+ Deteksi Dini,
+ Panen Lebih Optimal
+
+
+ Kenali penyakit tanaman tebu lebih cepat dengan teknologi Certainty Factor. Diagnosis akurat, penanganan tepat, hasil panen maksimal.
+
+
+
+
+
+
+
+
+
Diagnosis akurat dari 10 jenis penyakit tebu
+
+
+
+
+
+
Hasil diagnosis dalam hitungan menit
+
+
+
+
+
+
Rekomendasi penanganan dari pakar kebun
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
SiPakarTebu
+
Sistem Diagnosis Penyakit Tebu
+
+
+
+
+
Selamat Datang
+
Masuk untuk melanjutkan ke SiPakarTebu
+
+
+ @if ($errors->any())
+
+
+ {{ $errors->first() }}
+
+ @endif
+
+
+
+
+
+
+ Belum punya akun?
+
+ Daftar Gratis
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/auth/otp-verivy.blade.php b/resources/views/auth/otp-verivy.blade.php
new file mode 100644
index 0000000..063eb8a
--- /dev/null
+++ b/resources/views/auth/otp-verivy.blade.php
@@ -0,0 +1,158 @@
+{{-- resources/views/auth/otp-verify.blade.php --}}
+@extends('layouts.auth')
+
+@section('title', 'Verifikasi OTP')
+
+@section('content')
+
+ {{-- Steps indicator --}}
+
+
+ Masukkan Kode OTP
+
+ Kode 6 digit telah dikirim ke email kamu. Berlaku selama
+ 05:00 .
+
+
+ {{-- Alert success --}}
+ @if (session('success'))
+ {{ session('success') }}
+ @endif
+
+ {{-- Alert error --}}
+ @if ($errors->any())
+ {{ $errors->first() }}
+ @endif
+
+
+ 📧
+ Cek folder Spam jika kode tidak muncul dalam 1 menit.
+
+
+ {{-- Form verifikasi OTP --}}
+
+
+ {{-- Kirim ulang OTP --}}
+
+ Tidak menerima kode?
+
+
+
+
+ ← Ganti Email
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/auth/otp.blade.php b/resources/views/auth/otp.blade.php
new file mode 100644
index 0000000..7fb6d31
--- /dev/null
+++ b/resources/views/auth/otp.blade.php
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Verifikasi OTP - SiPakarTebu
+
+
+ @if(session('otp_debug'))
+
+ {{ session('otp_debug') }}
+
+@endif
+
+
+
+
+
+
+
Verifikasi OTP
+
Masukkan kode 6 digit yang dikirim ke email kamu
+
+ @if(session('otp_debug'))
+
{{ session('otp_debug') }}
+ @endif
+
+ @if(session('status'))
+
{{ session('status') }}
+ @endif
+
+ @if($errors->any())
+
{{ $errors->first() }}
+ @endif
+
+
+
+
+
+
← Kembali
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php
new file mode 100644
index 0000000..cc951a7
--- /dev/null
+++ b/resources/views/auth/register.blade.php
@@ -0,0 +1,448 @@
+
+
+
+
+
+ Register - SiPakarTebu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PAKAR TEBU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SiPakarTebu
+
+
+
+
+ 🌿 SISTEM PAKAR TEBU
+
+
+ Bergabung &
+ Mulai Diagnosa Sekarang
+
+
+ Daftar gratis dan dapatkan akses penuh ke sistem diagnosis penyakit tebu berbasis kecerdasan buatan.
+
+
+
+
+
+
+
+
Daftar gratis, tanpa biaya apapun
+
+
+
+
+
+
Riwayat diagnosis tersimpan otomatis
+
+
+
+
+
+
Akses kamus 10 penyakit tebu lengkap
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Buat Akun Baru
+
Isi data di bawah untuk mulai menggunakan SiPakarTebu
+
+
+ @if ($errors->any())
+
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+
+ @endif
+
+
+
+
+
+ Sudah punya akun?
+ Masuk di sini
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php
new file mode 100644
index 0000000..665555c
--- /dev/null
+++ b/resources/views/auth/reset-password.blade.php
@@ -0,0 +1,291 @@
+
+
+
+
+
+ Reset Password - SiPakarTebu
+
+
+
+
+
+ {{-- Header --}}
+
+
+ {{-- Body --}}
+
+
Reset Password
+
Masukkan password baru kamu
+
+ {{-- Error messages --}}
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+
{{ $error }}
+ @endforeach
+
+ @endif
+
+ {{-- Success message --}}
+ @if (session('status'))
+
+ {{ session('status') }}
+
+ @endif
+
+
+
+
← Kembali ke Login
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
new file mode 100644
index 0000000..86ab0d9
--- /dev/null
+++ b/resources/views/dashboard.blade.php
@@ -0,0 +1,579 @@
+@extends('layouts.app')
+
+@section('title', 'Dashboard')
+@section('page-title', 'Dashboard')
+
+@section('content')
+{{-- Sapaan --}}
+
+
+ Halo, {{ auth()->user()->name }}! 👋
+
+
+ Selamat datang kembali. Berikut ringkasan aktivitas diagnosa kamu hari ini.
+
+
+
+
+
+
+
+
+
+ Total Diagnosa
+
+
+
{{ $totalDiagnosis }}
+
+
+
+
+
+
+
+
+
+
+
+
+ Bulan Ini
+
+
+
+ {{ $monthlyData->where('month', (int) date('n'))->first()->count ?? 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Akurasi Rata-rata
+
+
+
{{ $avgAccuracy }}%
+
+
+
+
+
+
+
+
+
+
+
Statistik Diagnosa
+
+
+
+
+
+
+
+
Diagnosa Terbaru
+
+ {{-- Tampilan mobile: card --}}
+
+ @forelse($recentDiagnosis as $diagnosis)
+
+
+ {{ $diagnosis->created_at->format('d/m/Y') }}
+
+ {{ $diagnosis->confidence }}%
+
+
+
{{ $diagnosis->plant_name }}
+
{{ $diagnosis->disease_name }}
+
+ @empty
+
Belum ada diagnosa
+ @endforelse
+
+
+ {{-- Tampilan desktop: tabel --}}
+
+
+
+
+ Tanggal
+ Nama Tanaman
+ Penyakit
+ Akurasi
+
+
+
+ @forelse($recentDiagnosis as $diagnosis)
+
+ {{ $diagnosis->created_at->format('d/m/Y') }}
+ {{ $diagnosis->plant_name }}
+ {{ $diagnosis->disease_name }}
+
+
+ {{ $diagnosis->confidence }}%
+
+
+
+ @empty
+
+ Belum ada diagnosa
+
+ @endforelse
+
+
+
+
+
+
+
+
+
+
+
Statistik Total Diagnosa
+
Per bulan tahun {{ date('Y') }}
+
+
+
+
+
+
+
+ Total keseluruhan: {{ $totalDiagnosis }} diagnosa
+
+
+ Tutup
+
+
+
+
+
+
+
+
+
+
+ @foreach(['Min','Sen','Sel','Rab','Kam','Jum','Sab'] as $day)
+
{{ $day }}
+ @endforeach
+
+
+
+
+ Tutup
+
+
+
+
+
+
+
+
+
+
Grafik Akurasi
+
Rata-rata akurasi per bulan tahun {{ date('Y') }}
+
+
+
+
+
+
+
+ Akurasi rata-rata keseluruhan: {{ $avgAccuracy }}%
+
+
+ Tutup
+
+
+
+
+@endsection
+
+@push('scripts')
+
+
+
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/diagnosis/create.blade.php b/resources/views/diagnosis/create.blade.php
new file mode 100644
index 0000000..07b8251
--- /dev/null
+++ b/resources/views/diagnosis/create.blade.php
@@ -0,0 +1,485 @@
+@extends('layouts.app')
+
+@section('title', 'Diagnosa Penyakit')
+@section('page-title', 'Diagnosa Penyakit')
+
+@section('content')
+
+
+
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/diagnosis/index.blade.php b/resources/views/diagnosis/index.blade.php
new file mode 100644
index 0000000..bae15e2
--- /dev/null
+++ b/resources/views/diagnosis/index.blade.php
@@ -0,0 +1,71 @@
+@extends('layouts.app')
+
+@section('title', 'Riwayat Diagnosa')
+@section('page-title', 'Riwayat Diagnosa')
+
+@section('content')
+
+
+
+
+
+
+
+
+
+
+ Tanggal
+ Nama Tanaman
+ Penyakit
+ Akurasi
+ Aksi
+
+
+
+ @forelse($diagnoses as $diagnosis)
+
+ {{ $diagnosis->created_at->format('d/m/Y') }}
+ {{ $diagnosis->plant_name }}
+ {{ $diagnosis->disease_name }}
+
+
+ {{ $diagnosis->confidence }}%
+
+
+
+
+ Detail
+
+
+
+ @empty
+
+
+
+ Belum ada riwayat diagnosa
+
+
+ @endforelse
+
+
+
+
+
+ @if($diagnoses->hasPages())
+
+ {{ $diagnoses->links() }}
+
+ @endif
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/diagnosis/result.blade.php b/resources/views/diagnosis/result.blade.php
new file mode 100644
index 0000000..e2e14b5
--- /dev/null
+++ b/resources/views/diagnosis/result.blade.php
@@ -0,0 +1,319 @@
+@extends('layouts.app')
+
+@section('title', 'Hasil Diagnosa')
+@section('page-title', 'Hasil Diagnosa')
+
+@section('content')
+
+
+
+
+
+
+
+
Informasi Tanaman
+
Nama: {{ $diagnosis->plant_name }}
+
+
+
+
+
+ Gejala yang Dilaporkan ({{ count($diagnosis->symptoms ?? []) }} gejala)
+
+
+ @foreach($diagnosis->symptoms ?? [] as $kode => $data)
+ @php
+ $cfVal = $data['cf'];
+ if ($cfVal == 0.0) {
+ $cfColor = '#6b7280'; $cfBg = '#f3f4f6';
+ } elseif ($cfVal <= 0.2) {
+ $cfColor = '#991b1b'; $cfBg = '#fee2e2';
+ } elseif ($cfVal <= 0.4) {
+ $cfColor = '#92400e'; $cfBg = '#fef3c7';
+ } elseif ($cfVal <= 0.6) {
+ $cfColor = '#1d4ed8'; $cfBg = '#dbeafe';
+ } else {
+ $cfColor = '#2d6a4f'; $cfBg = '#d8f3dc';
+ }
+ @endphp
+
+ ✔ {{ $kode }} - {{ $data['nama'] }}
+
+ {{ number_format($cfVal, 1) }}
+
+
+ @endforeach
+
+
+
+
+
+
Hasil Diagnosa
+
Penyakit: {{ $diagnosis->disease_name }}
+
+ @php
+ $conf = $diagnosis->confidence;
+ $confVal = $conf / 100;
+ if ($confVal <= 0.20) {
+ $label = 'Tidak Terdeteksi'; $labelColor = '#6b7280'; $labelBg = '#f3f4f6';
+ } elseif ($confVal <= 0.40) {
+ $label = 'Rendah'; $labelColor = '#991b1b'; $labelBg = '#fee2e2';
+ } elseif ($confVal <= 0.60) {
+ $label = 'Sedang'; $labelColor = '#92400e'; $labelBg = '#fef3c7';
+ } elseif ($confVal <= 0.80) {
+ $label = 'Tinggi'; $labelColor = '#1d4ed8'; $labelBg = '#dbeafe';
+ } else {
+ $label = 'Sangat Tinggi'; $labelColor = '#2d6a4f'; $labelBg = '#d8f3dc';
+ }
+ @endphp
+
+ Kepercayaan: {{ $conf }}%
+
+ {{ $label }}
+
+
+
+
+
Penanganan:
+ @php $treatments = explode(';', $diagnosis->treatment); @endphp
+
+ @foreach($treatments as $item)
+ @if(trim($item) != '')
+ {{ trim($item) }}
+ @endif
+ @endforeach
+
+
+
+ Catatan:
+ Hasil diagnosa ini bersifat awal. Untuk penanganan yang lebih tepat dan akurat,
+ disarankan untuk berkonsultasi langsung dengan ahli tanaman atau penyuluh pertanian.
+
+
+
+
+
+
+
+
+
+
+
+
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/diseases/create.blade.php b/resources/views/diseases/create.blade.php
new file mode 100644
index 0000000..c583531
--- /dev/null
+++ b/resources/views/diseases/create.blade.php
@@ -0,0 +1,279 @@
+@extends('layouts.app')
+@section('title', isset($isEdit) ? 'Edit Penyakit' : 'Tambah Penyakit')
+@section('page-title', isset($isEdit) ? 'Edit Penyakit' : 'Tambah Penyakit Baru')
+
+@section('content')
+
+ Kembali
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/diseases/index.blade.php b/resources/views/diseases/index.blade.php
new file mode 100644
index 0000000..a654b33
--- /dev/null
+++ b/resources/views/diseases/index.blade.php
@@ -0,0 +1,165 @@
+@extends('layouts.app')
+@section('title', 'Kamus Penyakit')
+@section('page-title', 'Kamus Penyakit')
+
+@section('content')
+
+@if(session('status'))
+
+ {{ session('status') }}
+
+@endif
+
+
+
+
Total {{ $diseases->count() }} penyakit terdaftar
+ @if(auth()->user()->isAdmin())
+
+ Tambah Penyakit
+
+ @endif
+
+
+
+
+ @forelse($diseases as $disease)
+
+
+
+
+ @if($disease->photo)
+
+ @else
+
+ @endif
+
+
+
+
+
+
+ {{ $disease->code }}
+
+ {{ $disease->symptoms->count() }} gejala
+
+
+
+ {{ $disease->name }}
+
+
{{ $disease->latin_name }}
+
{{ $disease->description }}
+
+
+
+ @foreach($disease->symptoms->take(3) as $s)
+
+ {{ $s->code }}
+
+ @endforeach
+ @if($disease->symptoms->count() > 3)
+
+ +{{ $disease->symptoms->count() - 3 }} lainnya
+
+ @endif
+
+
+
+
+
+
+ @empty
+
+
+
Belum ada data penyakit
+
+ @endforelse
+
+
+
+
+
+
+
+
+
+
+ Hapus Penyakit?
+
+
+ Kamu akan menghapus penyakit
+
+
+
+ ⚠️ Data gejala & penanganan ikut terhapus dan tidak bisa dikembalikan.
+
+
+
+
+
+ Batal
+
+
+ @csrf
+ @method('DELETE')
+
+ Ya, Hapus
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/diseases/show.blade.php b/resources/views/diseases/show.blade.php
new file mode 100644
index 0000000..308aaba
--- /dev/null
+++ b/resources/views/diseases/show.blade.php
@@ -0,0 +1,165 @@
+@extends('layouts.app')
+@section('title', $disease->name)
+@section('page-title', 'Detail Penyakit')
+
+@section('content')
+
+
+
+
+
+
+
+
+
+
+ @if($disease->photo)
+
+ @else
+
+ @endif
+
+
+
{{ $disease->code }}
+
{{ $disease->name }}
+
{{ $disease->latin_name }}
+
{{ $disease->description }}
+
+
+
+
+
+
+
+
+
+
+ Gejala-Gejala
+
+
+ @foreach($disease->symptoms as $symptom)
+
+ @if($symptom->photo)
+
+ @else
+
+
+
+ @endif
+
+
{{ $symptom->code }}
+
{{ $symptom->name }}
+
CF: {{ $symptom->pivot->cf_value }}
+
+
+ @endforeach
+
+
+
+
+
+
+ Cara Penanganan
+
+
+ @foreach($disease->treatments as $i => $t)
+
+
{{ $i+1 }}
+
+
{{ $t->code }}
+
{{ $t->description }}
+
+
+ @endforeach
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hapus Penyakit?
+
+
+ Kamu akan menghapus penyakit
+
+
+
+ ⚠️ Data gejala & penanganan ikut terhapus dan tidak bisa dikembalikan.
+
+
+
+
+
+ Batal
+
+
+ @csrf
+ @method('DELETE')
+
+ Ya, Hapus
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/errors/403.blade.php b/resources/views/errors/403.blade.php
new file mode 100644
index 0000000..5a66fdd
--- /dev/null
+++ b/resources/views/errors/403.blade.php
@@ -0,0 +1,34 @@
+
+
+
+
+
+ Akses Ditolak - PlantCare
+
+
+
+
+
+
+
+
+
+
+
+ Akses Ditolak
+
+
+ Halaman ini hanya dapat diakses oleh Ahli Tanaman .
+
+
+ Akun kamu terdaftar sebagai Petani .
+
+
+ Kembali ke Dashboard
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/history/index.blade.php b/resources/views/history/index.blade.php
new file mode 100644
index 0000000..706d96e
--- /dev/null
+++ b/resources/views/history/index.blade.php
@@ -0,0 +1,255 @@
+@extends('layouts.app')
+
+@section('title', 'Riwayat Diagnosa')
+@section('page-title', 'Riwayat Diagnosa')
+
+@section('content')
+
+
+
+ {{-- Mobile: card --}}
+
+ @forelse($diagnoses as $index => $diagnosis)
+
+
+
+
{{ $diagnosis->plant_name }}
+
{{ $diagnosis->disease_name }}
+
+
+ {{ $diagnosis->confidence }}%
+
+
+
+
{{ $diagnosis->created_at->format('d/m/Y H:i') }}
+
+ Detail
+
+
+
+ @empty
+
+
+
Belum ada riwayat diagnosa
+
+ Mulai diagnosa pertama Anda
+
+
+ @endforelse
+
+
+ {{-- Desktop: tabel --}}
+
+
+
+
+ No
+ Tanggal
+ Nama Tanaman
+ Penyakit
+ Akurasi
+ Aksi
+
+
+
+ @forelse($diagnoses as $index => $diagnosis)
+
+ {{ $diagnoses->firstItem() + $index }}
+ {{ $diagnosis->created_at->format('d/m/Y H:i') }}
+ {{ $diagnosis->plant_name }}
+ {{ $diagnosis->disease_name }}
+
+
+ {{ $diagnosis->confidence }}%
+
+
+
+
+ Detail
+
+
+
+ @empty
+
+
+
+ Belum ada riwayat diagnosa
+
+ Mulai diagnosa pertama Anda
+
+
+
+ @endforelse
+
+
+
+
+ @if($diagnoses->hasPages())
+
+ {{ $diagnoses->links() }}
+
+ @endif
+
+
+
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
new file mode 100644
index 0000000..5ecfd2e
--- /dev/null
+++ b/resources/views/layouts/app.blade.php
@@ -0,0 +1,166 @@
+
+
+
+
+
+ @yield('title', 'SiPakarTebu')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @yield('page-title')
+
+
+
+
+
+
+
+
+ @yield('content')
+
+
+
+
+
+
+
+@stack('scripts')
+
+
+
\ No newline at end of file
diff --git a/resources/views/layouts/auth.blade.php b/resources/views/layouts/auth.blade.php
new file mode 100644
index 0000000..bc8418e
--- /dev/null
+++ b/resources/views/layouts/auth.blade.php
@@ -0,0 +1,234 @@
+{{-- resources/views/layouts/auth.blade.php --}}
+
+
+
+
+
+ @yield('title', 'PlantCare') - Sistem Diagnosis Penyakit Tanaman
+
+
+ @stack('styles')
+
+
+
+
+
+ @yield('content')
+
+
+@stack('scripts')
+
+
\ No newline at end of file
diff --git a/resources/views/notifications/index.blade.php b/resources/views/notifications/index.blade.php
new file mode 100644
index 0000000..71a562b
--- /dev/null
+++ b/resources/views/notifications/index.blade.php
@@ -0,0 +1,93 @@
+@extends('layouts.app')
+
+@section('title', 'Notifikasi')
+@section('page-title', 'Notifikasi')
+
+@section('content')
+
+
+
+
+
+
+
+
+
+ Semua Notifikasi
+ @if($notifications->where('is_read', false)->count() > 0)
+
+ {{ $notifications->where('is_read', false)->count() }} baru
+
+ @endif
+
+ @if($notifications->count() > 0)
+
+ @csrf
+ @method('DELETE')
+
+ Hapus Semua
+
+
+ @endif
+
+
+
+ @if($notifications->count() === 0)
+
+
+
Belum ada notifikasi
+
+ @else
+ @foreach($notifications as $notif)
+
+
+
+
+
+
+
+
+
+
+
{{ $notif->title }}
+ @if(!$notif->is_read)
+
+ @endif
+
+
{{ $notif->message }}
+
{{ $notif->created_at->diffForHumans() }}
+
+
+
+
+ @csrf
+ @method('DELETE')
+
+
+
+
+
+ @endforeach
+ @endif
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/profile/index.blade.php b/resources/views/profile/index.blade.php
new file mode 100644
index 0000000..2ab04ba
--- /dev/null
+++ b/resources/views/profile/index.blade.php
@@ -0,0 +1,285 @@
+@extends('layouts.app')
+@section('title', 'Profil')
+@section('page-title', 'Profil Saya')
+
+@section('content')
+
+
+ @if(session('status'))
+
+ {{ session('status') }}
+
+ @endif
+
+ @if($errors->any())
+
+ {{ $errors->first() }}
+
+ @endif
+
+ {{-- CARD 1: PROFIL --}}
+
+
+
Profil
+
+
+
+ @csrf
+
+ {{-- Avatar --}}
+
+
+ @if($user->photo)
+
+ @else
+
+ {{ strtoupper(substr($user->name, 0, 1)) }}
+
+ @endif
+
+
+
+
+
+
+
{{ $user->name }}
+
{{ $user->email }}
+
+
+ {{ $user->role === 'admin' ? 'Ahli Tanaman' : 'Petani' }}
+
+
+
+
+ {{-- Nama --}}
+
+ Nama
+
+
+
+ {{-- Email --}}
+
+ Email
+
+
+
+
+ Simpan Perubahan
+
+
+
+
+
+ {{-- CARD 2: PENGATURAN AKUN --}}
+
+
+
Pengaturan Akun
+
Keamanan akun dan akses
+
+
+
+ {{-- Ubah Password --}}
+
+
+
+
+
+
+
Ubah Kata Sandi
+
Perbarui password akun kamu
+
+
+
+ Ubah
+
+
+
+ {{-- Manajemen Perangkat --}}
+
+
+
+
+
+
+
Manajemen Perangkat
+
Lihat perangkat yang mengakses akun
+
+
+
+ Lihat
+
+
+
+ {{-- Hapus Akun --}}
+
+
+
+
+
+
+
Hapus Akun
+
Hapus akun dan semua data kamu secara permanen
+
+
+
+ Hapus
+
+
+
+
+
+ {{-- CARD 3: LAIN-LAIN --}}
+
+
+
Lain-lain
+
+
+
+ {{-- Logout --}}
+
+
+
+
+
+
+
Logout
+
Keluar dari akun ini
+
+
+
+ @csrf
+
+ Logout
+
+
+
+
+ {{-- Bantuan --}}
+
+
+
+
+
+
+
Bantuan & Dukungan
+
Hubungi kami jika ada masalah
+
+
+
+ Hubungi
+
+
+
+
+
+
+
+
+{{-- Modal Manajemen Perangkat --}}
+
+
+
+
Manajemen Perangkat
+
+
+
+
+
+
+
+
+
Perangkat Ini
+
Sesi aktif sekarang
+
+
Aktif
+
+
Hanya menampilkan sesi aktif saat ini
+
+
+ Tutup
+
+
+
+
+{{-- Modal Hapus Akun --}}
+
+
+
+
+
+
+
Hapus Akun?
+
Semua data kamu akan dihapus permanen dan tidak bisa dikembalikan.
+
+
+
+ Batal
+
+
+ @csrf
+ @method('DELETE')
+
+ Ya, Hapus
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php
new file mode 100644
index 0000000..eb6ab3f
--- /dev/null
+++ b/resources/views/users/index.blade.php
@@ -0,0 +1,274 @@
+@extends('layouts.app')
+@section('title', 'Kelola User')
+@section('page-title', 'Kelola User')
+
+@section('content')
+
+@if(session('status'))
+
+ {{ session('status') }}
+
+@endif
+
+@if($errors->any())
+
+ {{ $errors->first() }}
+
+@endif
+
+{{-- Kartu Statistik --}}
+
+
+
Total User
+
+ {{ $totalAdmin + $totalUser }}
+
+
+
+
Ahli Tanaman
+
+ {{ $totalAdmin }}
+
+
+
+
Petani
+
+ {{ $totalUser }}
+
+
+
+
+{{-- Filter & Search --}}
+
+
+
+ Cari
+
+
+
+ Role
+
+ Semua
+ Ahli Tanaman
+ Petani
+
+
+
+ Cari
+
+ @if(request('search') || request('role'))
+
+ Reset
+
+ @endif
+
+
+
+{{-- Tabel User --}}
+
+
+ {{-- Mobile: card --}}
+
+ @forelse($users as $user)
+
+
+
+ {{ strtoupper(substr($user->name, 0, 1)) }}
+
+
+
+ {{ $user->name }}
+ @if($user->id === auth()->id())
+ (Kamu)
+ @endif
+
+
{{ $user->email }}
+
+
+ {{ $user->role === 'admin' ? 'Ahli' : 'Petani' }}
+
+
+ @if($user->id !== auth()->id())
+
+
+ @csrf
+ @method('PATCH')
+
+
+
+ Jadikan {{ $user->role === 'admin' ? 'Petani' : 'Ahli' }}
+
+
+
+
+
+
+ @endif
+
+ @empty
+
+
+ Tidak ada user ditemukan
+
+ @endforelse
+
+
+ {{-- Desktop: tabel --}}
+
+
+
+
+ User
+ Email
+ Role
+ Bergabung
+ Aksi
+
+
+
+ @forelse($users as $user)
+
+
+
+
+ {{ strtoupper(substr($user->name, 0, 1)) }}
+
+
+
+ {{ $user->name }}
+ @if($user->id === auth()->id())
+ (Kamu)
+ @endif
+
+
+
+
+ {{ $user->email }}
+
+
+
+ {{ $user->role === 'admin' ? 'Ahli Tanaman' : 'Petani' }}
+
+
+ {{ $user->created_at->format('d M Y') }}
+
+ @if($user->id !== auth()->id())
+
+
+ @csrf
+ @method('PATCH')
+
+
+
+ Jadikan {{ $user->role === 'admin' ? 'Petani' : 'Ahli Tanaman' }}
+
+
+
+
+
+
+ @else
+ —
+ @endif
+
+
+ @empty
+
+
+
+ Tidak ada user ditemukan
+
+
+ @endforelse
+
+
+
+
+ @if($users->hasPages())
+
+ {{ $users->links() }}
+
+ @endif
+
+
+{{-- Modal Konfirmasi Hapus User --}}
+
+
+
+
+
+
+
Hapus User?
+
+ Kamu akan menghapus akun
+
+
+
⚠️ Semua data diagnosa user ikut terhapus.
+
+
+
+ Batal
+
+
+ @csrf
+ @method('DELETE')
+
+ Ya, Hapus
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php
new file mode 100644
index 0000000..1956ce7
--- /dev/null
+++ b/resources/views/welcome.blade.php
@@ -0,0 +1,870 @@
+
+
+
+
+
+
+ SiPakarTebu — Diagnosa Penyakit Tanaman Tebu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SiPakarTebu
+
+
+
+
+
+
+
+
Didukung data oleh pakar
+
Kenali PenyakitTanaman Tebu Lebih Cepat
+
+ SiPakarTebu menggunakan kecerdasan buatan berbasis Certainty Factor untuk mendiagnosa penyakit tanaman secara akurat — cukup jawab beberapa pertanyaan gejala.
+
+
+
+
+ 90%
+ Akurasi Rata-rata
+
+
+ 10
+ Jenis Penyakit
+
+
+ 1 mnt
+ Waktu Diagnosa
+
+
+
+
+
+
+
✓ Diagnosa Selesai
+
+
⚠️ Terdeteksi
+
Luka Api (Leaf Scald)
+
Penyakit disebabkan bakteri Xanthomonas albilineans
+
+
Tingkat Kepastian 78%
+
+
+
+
Kesesuaian Gejala 91%
+
+
+
+
+
+
+
+
+ Fitur Unggulan
+ Teknologi Cermat untukKebun Sehat Kamu
+ Sistem pakar kami menggabungkan pengetahuan ahli pertanian dengan teknologi untuk hasil diagnosa yang dapat dipercaya.
+
+
🔬
Diagnosa Akurat
Menggunakan metode Certainty Factor yang telah terbukti untuk mengidentifikasi penyakit tanaman dengan tingkat akurasi tinggi.
+
⚡
Hasil Instan
Dapatkan hasil diagnosa dalam hitungan menit. Cukup jawab pertanyaan gejala dan sistem kami akan menganalisis secara otomatis.
+
📋
Rekomendasi Penanganan
Setiap hasil diagnosa disertai panduan penanganan lengkap yang bisa langsung dipraktikkan di kebun kamu.
+
📊
Riwayat Diagnosa
Simpan dan pantau semua diagnosa sebelumnya. Lacak perkembangan kesehatan tanaman kamu dari waktu ke waktu.
+
🌱
Database Penyakit Lengkap
Mencakup lebih dari 5 jenis penyakit umum pada tanaman dengan gejala dan penanganan yang terperinci.
+
🛡️
Data Aman & Privat
Data diagnosa kamu tersimpan dengan aman. Hanya kamu yang bisa mengakses riwayat dan hasil diagnosa milik kamu.
+
+
+
+
+
+
+
+
BERITA & RISET TERKINI
+
Dari Sumber Terpercaya
+
Artikel terbaru seputar pertanian dan penyakit tanaman dari media & lembaga penelitian nasional
+
+ @if(isset($articles) && count($articles) > 0)
+
+
+ @foreach($articles as $i => $article)
+
+ @endforeach
+
+ ← Sebelumnya
+ 1 / {{ count($articles) }}
+ Selanjutnya →
+
+
+ @else
+
+ @endif
+
+
+
+
+
+ Mulai Sekarang
+ Jaga Tanaman Kamu tetap Sehat & Subur
+ Daftar gratis sekarang dan mulai diagnosa pertama kamu dalam hitungan menit. Tidak perlu kartu kredit.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Pilih Gejala
+
Pilih minimal 4 gejala yang tampak pada tanaman
+
+
+
+
+
+ ⏳ Memuat daftar gejala...
+
+
+
+
+
+ Dipilih: 0 gejala
+ (min. 4)
+
+
+
+
+ Lanjut →
+
+
+
+
+
+
Tingkat Keyakinan
+
Seberapa yakin kamu melihat gejala ini pada tanaman?
+
+
+
+
+ ← Kembali
+ Lanjut →
+
+
+
+
+
+
Konfirmasi
+
Periksa kembali sebelum diagnosa dimulai
+
+
+
+
+ ← Kembali
+ Diagnosa Sekarang
+
+
+
+
+
+
+
+
Menganalisis gejala...
+
+
+
+
+
+
Hasil Diagnosa
+
Berdasarkan gejala yang kamu pilih
+
+
+
+
+
+
+
+
Penanganan yang Disarankan
+
• Semprot fungisida berbasis tembaga...
+
• Cabut dan musnahkan tanaman yang terinfeksi berat
+
• Lakukan rotasi tanaman minimal 2 musim
+
Kemungkinan Penyakit Lain
+
• ██████████ (██%)
+
• ██████████ (██%)
+
+
+
+
+
+ ↩ Ulangi Diagnosa
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/routes/console.php b/routes/console.php
new file mode 100644
index 0000000..3c9adf1
--- /dev/null
+++ b/routes/console.php
@@ -0,0 +1,8 @@
+comment(Inspiring::quote());
+})->purpose('Display an inspiring quote');
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 0000000..a0f326d
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,104 @@
+name('guest.symptoms');
+Route::post('/guest/diagnosis', [GuestDiagnosisController::class, 'process'])->name('guest.diagnosis');
+
+
+// ── Halaman utama ─────────────────────────────────────────────────
+Route::get('/', [WelcomeController::class, 'index'])->name('home');
+
+Route::delete('/profile/delete', [ProfileController::class, 'destroy'])->name('profile.destroy')->middleware('auth');
+
+// ── Guest routes ──────────────────────────────────────────────────
+Route::middleware('guest')->group(function () {
+ Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
+ Route::get('/register', [AuthController::class, 'showRegister'])->name('register');
+ Route::post('/login', [AuthController::class, 'login']);
+ Route::post('/register',[AuthController::class, 'register']);
+
+ Route::get('/forgot-password', [ForgotPasswordController::class, 'showEmailForm'])->name('password.email');
+ Route::post('/forgot-password', [ForgotPasswordController::class, 'sendOtp'])->name('password.send-otp');
+ Route::get('/forgot-password/otp', [ForgotPasswordController::class, 'showOtpForm'])->name('password.otp.form');
+ Route::post('/forgot-password/otp', [ForgotPasswordController::class, 'verifyOtp'])->name('password.otp.verify');
+ Route::post('/forgot-password/otp/resend', [ForgotPasswordController::class, 'resendOtp'])->name('password.otp.resend');
+ Route::get('/forgot-password/reset', [ForgotPasswordController::class, 'showResetForm'])->name('password.reset.form');
+ Route::post('/forgot-password/reset', [ForgotPasswordController::class, 'resetPassword'])->name('password.reset');
+});
+
+// ── Logout ────────────────────────────────────────────────────────
+Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
+
+// ── Authenticated routes ─────────────────────────────────────────
+Route::middleware('auth')->group(function () {
+
+ // Dashboard
+ Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
+
+ // Diagnosis
+ Route::prefix('diagnosis')->name('diagnosis.')->group(function () {
+ Route::get('/', [DiagnosisController::class, 'index'])->name('index');
+ Route::get('/create', [DiagnosisController::class, 'create'])->name('create');
+ Route::post('/', [DiagnosisController::class, 'store'])->name('store');
+ Route::get('/{id}/result', [DiagnosisController::class, 'result'])->name('result');
+ });
+
+ // Riwayat
+ Route::get('/riwayat', [HistoryController::class, 'index'])->name('history');
+
+ // ── KAMUS (READ dulu) ─────────────────────────────
+ Route::get('/kamus', [DiseaseController::class, 'index'])->name('diseases.index');
+
+ // ⚠️ JANGAN TARUH {disease} DI ATAS
+ // nanti bentrok dengan /kamus/tambah
+
+ // Notifikasi
+ Route::get('/notifications', [NotificationController::class, 'index'])->name('notifications');
+ Route::post('/notifications/{id}/read', [NotificationController::class, 'markRead'])->name('notifications.read');
+ Route::delete('/notifications/{id}', [NotificationController::class, 'destroy'])->name('notifications.destroy');
+ Route::delete('/notifications', [NotificationController::class, 'destroyAll'])->name('notifications.destroyAll');
+
+ // Profil
+ Route::get('/profile', [ProfileController::class, 'show'])->name('profile');
+ Route::post('/profile', [ProfileController::class, 'update'])->name('profile.update');
+ Route::post('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.password');
+});
+
+// ── ADMIN ONLY ───────────────────────────────────────────────────
+Route::middleware(['auth', 'role:admin'])->group(function () {
+
+ Route::get('/kamus/tambah', [DiseaseController::class, 'create'])->name('diseases.create');
+ Route::post('/kamus', [DiseaseController::class, 'store'])->name('diseases.store');
+ Route::delete('/kamus/{disease}', [DiseaseController::class, 'destroy'])->name('diseases.destroy');
+ Route::get('/diseases/{disease}/edit',[DiseaseController::class, 'edit'])->name('diseases.edit');
+ Route::put('/diseases/{disease}', [DiseaseController::class, 'update'])->name('diseases.update');
+
+ // User management
+ Route::get('/users', [UserController::class, 'index'])->name('users.index');
+ Route::patch('/users/{user}/role', [UserController::class, 'updateRole'])->name('users.updateRole');
+ Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
+});
+
+// ── TARUH PALING BAWAH (ANTI BENTROK) ───────────────────────────
+Route::get('/kamus/{disease}', [DiseaseController::class, 'show'])
+ ->middleware('auth')
+ ->name('diseases.show');
+
+// Redirect /diseases/{id} ke /kamus/{id} supaya tidak 405
+Route::get('/diseases/{disease}', function($disease) {
+ return redirect()->route('diseases.show', $disease);
+})->middleware('auth');
\ No newline at end of file